基于react、redux的todoLists

上篇的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的截图的,我一直傻乎乎的发了,结果出来没图,看上去好傻,没代码的文章....惊恐

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值