上篇的counter例子其实太简单,action太少,reducer也只有一个,只能说一个初窥的样例,但是这个todoLists例子应该初学redux的人最常遇见,因为其五脏虽小,但是几乎使用了redux和react-redux该使用的所有特性。
按照我的理解,使用redux起来最难的是如何定义数据结构,这个就直接决定了你的项目的可维护性,所以写的顺序应该是actions => components => reducers => containers
目录结构:
1.首先是actions,actions是用户的响应,可以通过dispatch(actions)来传递给store,实际上定义这个是很难的,至少如果脱离开来actions是在所有环节当中最重要的,你要定义一个通用的且适合的actions我感觉我目前还很难做到。 actions本身是一个js对象,你可以通过actions creator来创建它。
export const ADD_TODO = 'ADD_TODO';
export const TOGGLE_TODO = 'TOGGLE_TODO';
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER';
export const visibilityType = {
SHOW_ALL : 'SHOW_ALL',
SHOW_COMPLETED: 'SHOW_COMPLETED',
SHOW_ACTIVE : 'SHOW_ACTIVE'
};
export const addTodo = (text) => ({
type: ADD_TODO,
text
});
export const toggleTodo = (index) => ({
type: TOGGLE_TODO,
index
});
export const setVisibilityFilter = (filter) => ({
type: SET_VISIBILITY_FILTER,
filter
});
这边addTodo、toggleTodo、setVisibilityFilter三个func是actions creators,各自返回一个对象。分析一下我们要做的todoLists,可以分成三部分,第一部分输入表单,所以关键字是text字符串;第二部分是输入显示的内容以及点击完成,所以关键字是index点击的索引;第三部分是筛选器,所以关键字是filter筛选条件,是字符串好了。
2.然后是components,即为UI组件,先写UI组件是为了可以确定props的结构即state的结构,利于理通思路。
import React from 'react';
import AddTodo from './AddTodo';
import TodoList from './TodoList';
import Filter from './Filter';
import {addTodo, toggleTodo, setVisibilityFilter} from '../actions/createAction';
class App extends React.Component {
render() {
const {dispatch, todos, visibilityFilter} = this.props;
return (
<div>
<AddTodo
onAddClick = {text => {
dispatch(addTodo(text));
}}
/>
<TodoList
todos = {todos}
onToggleClick = {index => {
dispatch(toggleTodo(index));
}}
/>
<Filter
filter = {visibilityFilter}
onFilterChange = {filter => {
dispatch(setVisibilityFilter(filter));
}}
/>
</div>
);
}
}
export default App;
这边就按照actions那样定义的有三种UI组件,分别是表单、显示和筛选。
AddTodo组件
import React from 'react';
class AddTodo extends React.Component {
render() {
let input;
return (
<form onSubmit = {
(e) => {
if (input.value.trim() === '') {
return;
}
e.preventDefault();
this.props.onAddClick(input.value);
input.value = '';
}
}>
<input type="text" required ref = {node => {
input = node;
}} />
<input type="submit" value="add" />
</form>
);
}
}
export default AddTodo;
TodoList组件
import React from 'react';
import Todo from './Todo';
class TodoList extends React.Component {
render() {
return (
<ul>
{
this.props.todos.map((todo, index) =>
<Todo key = {index}
{...todo}
onClick = {() => {
this.props.onToggleClick(index);
}}
/>
)
}
</ul>
);
}
}
export default TodoList;
Todo组件
import React from 'react';
class Todo extends React.Component {
render() {
return (
<li
onClick = {this.props.onClick}
style = {{
textDecoration: this.props.completed ? 'line-through' : 'none',
cursor: this.props.completed ? 'default' : 'pointer',
color: this.props.completed ? '#f00' : '#000'
}}
>
{this.props.text}
</li>
);
}
}
export default Todo;
Filter组件
import React from 'react';
import {visibilityType} from '../actions/createAction';
class Filter extends React.Component {
renderFilter(filter, name) {
if (filter === this.props.filter) {
return name;
} else {
return (
<a href="#" onClick = {
(e) => {
e.preventDefault();
this.props.onFilterChange(filter);
}
}>
{name}
</a>
);
}
}
render() {
return (
<div>
请选择:
{' '}
{this.renderFilter(visibilityType.SHOW_ALL, 'All')}
{' ,'}
{this.renderFilter(visibilityType.SHOW_COMPLETED, 'completed')}
{' ,'}
{this.renderFilter(visibilityType.SHOW_ACTIVE, 'active')}
</div>
);
}
}
export default Filter;
3.写完componets后,大致知道了state的结构,接下来开始写reducers,reducers是对通过dispatch传过来的actions进行处理,但是有个规定是,传过来是什么,返回就是一个新的同类型的数据,不能掺杂有副作用的运算,简单理解就是传过来时String那传过去也必须是String,传过来时Array,那穿回去也必须是Array。
子reducer todo.js
import {ADD_TODO, TOGGLE_TODO} from '../actions/createAction';
const todos = (state = [], action) => {
switch (action.type) {
case ADD_TODO:
return [
...state,
{
text: action.text,
completed: false
}
]
case TOGGLE_TODO:
return state.map((todo, index) => {
if (action.index === index) {
return Object.assign({}, todo,{
completed: !todo.completed
})
}
return todo;
});
default:
return state;
}
}
export default todos;
子reducer setFilter.js
import {visibilityType} from '../actions/createAction';
import {SET_VISIBILITY_FILTER} from '../actions/createAction';
const setFilter = (state = visibilityType.SHOW_ALL, action) => {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return action.filter;
default:
return state;
}
}
export default setFilter;
总reducer
import {combineReducers} from 'redux';
import todos from './todos';
import setFilter from './setFilter';
const todoApp = combineReducers({
todos,
setFilter
});
export default todoApp;
然后使用redux提供的combinReducers 整合成一个最终的reducer,注意,这边定义的参数非常重要,初始化props和dispatch传回actions的时候都需要使用这里面的参数,而且必须是同名,如果不同名就会有问题。
4.接下来就可以写container就可以开始写了,因为state结构都已经有了,reducer传回的值类型也有了,container写起来就会比较快。
container是UI组件connect转换而成,
const Container = connect(
mapStatesToProps,
MapDisPatchToProps
)(App);
其中如字面意思,App是UI组件mapStateToProps是将states放给UI即UI的props的函数,mapDispatchToProps是将actions通过dispatch穿回来的函数。
import React from 'react';
import {connect} from 'react-redux';
import App from '../components/App';
import {visibilityType} from '../actions/createAction';
const selectTodo = (todos, visibilityFilter) => {
switch (visibilityFilter) {
case visibilityType.SHOW_ALL:
return todos;
case visibilityType.SHOW_COMPLETED:
return todos.filter(todo => todo.completed);
case visibilityType.SHOW_ACTIVE:
return todos.filter(todo => !todo.completed);
default:
return todos;
}
}
const mapStatesToProps = (state) => ({
todos: selectTodo(state.todos, state.setFilter),
visibilityFilter: state.setFilter
})
const Container = connect(
mapStatesToProps
)(App);
export default Container;
注意,这边mapStateToProps函数中传回一个对象,因为前面reducer已经定义了state类型,所以必须严格按照前面来写,
就是state.todos 和 state.setFilter 两个。
5.最后是main.js,也就是render的地方。
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import {createStore} from 'redux';
import Container from './containers/index';
import todoApp from './reducers/index';
const store = createStore(todoApp);
ReactDOM.render(
<Provider store = {store}>
<Container />
</Provider>,
document.getElementById('app')
)
这里使用了redux提供的createStore函数,接收一个reducer处理函数,生成一个也是唯一的一个store,存储所有的数据状态,然后使用react-redux提供的Provider类将store传入,最后Container容器组件包在里面,实现状态的控制。
redux中文文档:http://www.redux.org.cn/docs/basics/Reducers.html
写完了,希望各位大大觉得满意的话可以加个活跃,不好可以提出来,毕竟我也还是个菜鸡,接下去会更新后续的小demo的- - |。
PS:哎呦我去,csdn原来不能传QQ的截图的,我一直傻乎乎的发了,结果出来没图,看上去好傻,没代码的文章....