写在开始
前篇中,我们介绍了 Redux ,对 Store 、 Action 、Reducer 有了初步的了解。并配合 react-redux ,梳理了其工作流程,结合使用了 Provider 、 connect() 。
接下来,我们完成开篇任务中的最后一项,**过滤显示 TODO **。
源码:https://github.com/eylu/web-lib/tree/master/ReactReduxDemo/app_step3
开发
我们所做任务如下:
- 在入口文件添加一个新的初始
state
:
与todos
平级,做为当前过滤条件。 - 创建一个子组件
TodoFilterComponent
:
包含3个过滤按钮All
、Undo
、Finish
,并接收一个属性为当前过滤条件,过滤按钮与当前过滤条件相同时,过滤按钮不可点击。 - 修改容器组件
HomeContainer
:
引入过滤组件,并且可以设置当前过滤条件,根据过滤条件显示不同状态的 TODO 项。 - 添加
Action
:
定义ActionCreator
生成设置当前过滤条件的Action
。 - 添加
Reducer
:
可以设置当前的过滤条件。
1、添加新初始 state
ReactReduxDemo/app/index.js
文件,修改如下:
import React, { Component } from 'react';
import {
View,
StyleSheet,
} from 'react-native';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import reducers from './reducers/index';
import HomeContainer from './containers/home.container';
// 这是初始数据
const initState = {
todos: [
{title:'吃早饭',status:true},
{title:'打篮球',status:false},
{title:'修电脑',status:false},
],
filter: 'All', // 'All'|'Undo'|'Finish' // 添加新的初始 `state`
};
let store = createStore(reducers, initState);
export default class RootWrapper extends Component{
render(){
return (
<Provider store={store}>
<View style={styles.wrapper}>
<HomeContainer />
</View>
</Provider>
);
}
}
const styles = StyleSheet.create({
wrapper: {
flex: 1,
marginTop: 20,
},
});
2、创建子组件 TodoFilterComponent
新建文件 ReactReduxDemo/app/components/todo-filter.component.js
, 如下:
import React, { Component } from 'react';
import {
View,
Text,
TouchableOpacity,
StyleSheet,
} from 'react-native';
export default class TodoFilterComponent extends Component{
constructor(props){
super(props);
}
filterTodo(filter){
this.props.filterTodo && this.props.filterTodo(filter);
}
renderFilter(filter){
if(filter==this.props.filter){
return (
<Text style={[styles.filter,styles.filterCurrent]}>{filter}</Text>
);
}
return (
<TouchableOpacity onPress={()=>{this.filterTodo(filter)}}>
<Text style={styles.filter}>{filter}</Text>
</TouchableOpacity>
);
}
render(){
return (
<View style={styles.wrapper}>
{this.renderFilter('All')}
{this.renderFilter('Undo')}
{this.renderFilter('Finish')}
</View>
);
}
}
const styles = StyleSheet.create({
wrapper: {
flexDirection: 'row',
paddingLeft: 20,
paddingTop: 20,
},
filter: {
marginRight: 20,
textDecorationLine: 'underline',
},
filterCurrent:{
color: 'gray',
textDecorationLine: 'none',
},
});
3、容器组件HomeContainer
我们在其中引入子组件TodoFilterComponent
,并添加过滤方法,此方法的功能为设置当前过滤条件。
ReactReduxDemo/app/containers/home.container.js
文件,修改如下:
import React, { Component } from 'react';
import {
View,
Text
} from 'react-native';
import { connect } from 'react-redux';
import { changeTodoStatus, addNewTodo, filterTodoList } from '../actions/index'; // 引入 action
import TodoFormComponent from '../components/todo-form.component';
import TodoListComponent from '../components/todo-list.component';
import TodoFilterComponent from '../components/todo-filter.component'; // 引入子组件
class HomeContainer extends Component{
constructor(props){
super(props);
}
addTodo(text){
let { dispatch } = this.props;
dispatch(addNewTodo(text));
}
toggleTodo(index){
let { dispatch } = this.props;
dispatch(changeTodoStatus(index));
}
filterTodo(filter){
let { dispatch } = this.props; // 从 props 里解构出 dispatch
dispatch(filterTodoList(filter)); // 执行过滤方法
}
render(){
return (
<View>
<TodoFormComponent addTodo={(text)=>{this.addTodo(text)}} />
<TodoListComponent todoList={this.props.todoList} toggleTodo={(index)=>{this.toggleTodo(index)}} />
<TodoFilterComponent filter={this.props.currentFilter} filterTodo={(filter)=>{this.filterTodo(filter)}} /> // 渲染子组件(注释会报错,请删除注释)
</View>
);
}
}
// 基于全局 state ,哪些 state 是我们想注入的 props
function mapStateToProps(state){
return {
todoList: state.todos,
currentFilter: state.filter, // 注入新的 state 到 props
}
}
export default connect(mapStateToProps)(HomeContainer);
4、修改 Action
添加新的 action
类型,新的 action
创建函数。
ReactReduxDemo/app/actions/index.js
文件,修改如下:
/*********************************** action 类型常量 *************************************/
/**
* 更改 TODO 状态
* @type {String}
*/
export const TOGGLE_TODO_STATUS = 'TOGGLE_TODO_STATUS';
export const ADD_NEW_TODO = 'ADD_NEW_TODO';
export const SET_FILTER = 'SET_FILTER'; // 添加新的 action 类型
/*********************************** action 创建函数 *************************************/
/**
* 更改 TODO 状态
* @param {Number} index TODO索引
* @return {Object} action
*/
export function changeTodoStatus(index){
return {type: TOGGLE_TODO_STATUS, index};
}
export function addNewTodo(text){
return {type: ADD_NEW_TODO, text};
}
export function filterTodoList(filter){ // 添加新的 action 创建函数
return {type: SET_FILTER, filter};
};
5、修改 Reducer
引用新的action
类型,添加新的reducer
函数,并在 combineReducers
中使用。
ReactReduxDemo/app/reducers/index.js
文件,修改如下:
import { combineReducers } from 'redux';
import { TOGGLE_TODO_STATUS, ADD_NEW_TODO, SET_FILTER } from '../actions/index'; // 引入 action ,使用 action 类型常量
function todoList(state=[], action){
switch(action.type){
case TOGGLE_TODO_STATUS:
var todo = state[action.index];
return [
...state.slice(0, action.index),
Object.assign({}, todo, {
status: !todo.status
}),
...state.slice(action.index + 1)
];
case ADD_NEW_TODO:
return [
...state,
{
title: action.text,
status: false,
}
];
default :
return state;
}
}
function setFilter(state='', action){ // 定义了新的 reducer
switch(action.type){
case SET_FILTER:
return action.filter;
default :
return state;
}
}
const reducers = combineReducers({
todos: todoList,
filter: setFilter, // 添加了新的 reducer combine
});
export default reducers;
运行项目,是否显示出了过滤按钮呢?点击试试看,能否过滤?(暂时还不可以,我们需要写方法对数据进行过滤)
过滤 TODO 显示
todo 列表有了,当前过滤条件也可以设置了。这里,我们需要完成根据当前过滤条件对 TODO 列表进行过滤显示。
我们知道,TODO 列表的显示,是根据容器组件HomeContainer
给的数据来显示的,它并不知道数据的具体来源,给它什么数据它就显示什么数据(这也正是我们想要的功能)。
我们就会想到,将容器组件HomeContainer
的 TODO 列表数据做修改,是不是就可以了呢?是的,确实是这样。
我们来看容器组件HomeContainer
部分代码
// 基于全局 state ,哪些 state 是我们想注入的 props
function mapStateToProps(state){
return {
todoList: state.todos, // <---- 这里就是给子组件展示的数据
currentFilter: state.filter,
}
}
我们将其做一些修改过滤,就OK了。
ReactReduxDemo/app/containers/home.container.js
文件,修改如下:
import React, { Component } from 'react';
import {
View,
Text
} from 'react-native';
import { connect } from 'react-redux';
import { changeTodoStatus, addNewTodo, filterTodoList } from '../actions/index';
import TodoFormComponent from '../components/todo-form.component';
import TodoListComponent from '../components/todo-list.component';
import TodoFilterComponent from '../components/todo-filter.component';
class HomeContainer extends Component{
constructor(props){
super(props);
}
addTodo(text){
let { dispatch } = this.props;
dispatch(addNewTodo(text));
}
toggleTodo(index){
let { dispatch } = this.props;
dispatch(changeTodoStatus(index));
}
filterTodo(filter){
let { dispatch } = this.props;
dispatch(filterTodoList(filter));
}
render(){
return (
<View>
<TodoFormComponent addTodo={(text)=>{this.addTodo(text)}} />
<TodoListComponent todoList={this.props.todoList} toggleTodo={(index)=>{this.toggleTodo(index)}} />
<TodoFilterComponent filter={this.props.currentFilter} filterTodo={(filter)=>{this.filterTodo(filter)}} />
</View>
);
}
}
const getFilterTodos = (todos, filter) => { // 定义 TODO 过滤方法,返回新的数据
switch (filter) {
case 'All':
return todos;
case 'Undo':
return todos.filter( todo => !todo.status);
case 'Finish':
return todos.filter( todo => todo.status);
default:
throw new Error('Unknown filter: ' + filter);
}
}
// 基于全局 state ,哪些 state 是我们想注入的 props
function mapStateToProps(state){
return {
todoList: getFilterTodos(state.todos, state.filter), // 注入props时,调用 TODO 过滤方法
currentFilter: state.filter,
}
}
export default connect(mapStateToProps)(HomeContainer);
运行项目,是否可以对 TODO 列表进行过滤了呢?恭喜你!!
到目前为止,我们的 ReactNative + Redux 开发示例告一小段落。
后面会有中间件、异步的的使用方法。