🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 示例: Todo 列表 这是我们在[基础教程](#)里开发的迷你型的任务管理应用的完整源码。 ### 入口文件 #### `index.js` ~~~ import React from 'react'; import { createStore } from 'redux'; import { Provider } from 'react-redux'; import App from './containers/App'; import todoApp from './reducers'; let store = createStore(todoApp); let rootElement = document.getElementById('root'); React.render( // 为了解决 React 0.13 的问题, // 一定要把 child 用函数包起来。 <Provider store={store}> {() => <App />} </Provider>, rootElement ); ~~~ ### Action 创建函数和常量 #### `actions.js` ~~~ /* * action types */ export const ADD_TODO = 'ADD_TODO'; export const COMPLETE_TODO = 'COMPLETE_TODO'; export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER' /* * 其它的常量 */ export const VisibilityFilters = { SHOW_ALL: 'SHOW_ALL', SHOW_COMPLETED: 'SHOW_COMPLETED', SHOW_ACTIVE: 'SHOW_ACTIVE' }; /* * action 创建函数 */ export function addTodo(text) { return { type: ADD_TODO, text }; } export function completeTodo(index) { return { type: COMPLETE_TODO, index }; } export function setVisibilityFilter(filter) { return { type: SET_VISIBILITY_FILTER, filter }; } ~~~ ### Reducers #### `reducers.js` ~~~ import { combineReducers } from 'redux'; import { ADD_TODO, COMPLETE_TODO, SET_VISIBILITY_FILTER, VisibilityFilters } from './actions'; const { SHOW_ALL } = VisibilityFilters; function visibilityFilter(state = SHOW_ALL, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return action.filter; default: return state; } } function todos(state = [], action) { switch (action.type) { case ADD_TODO: return [...state, { text: action.text, completed: false }]; case COMPLETE_TODO: return [ ...state.slice(0, action.index), Object.assign({}, state[action.index], { completed: true }), ...state.slice(action.index + 1) ]; default: return state; } } const todoApp = combineReducers({ visibilityFilter, todos }); export default todoApp; ~~~ ### 智能组件 #### `containers/App.js` ~~~ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import { addTodo, completeTodo, setVisibilityFilter, VisibilityFilters } from '../actions'; import AddTodo from '../components/AddTodo'; import TodoList from '../components/TodoList'; import Footer from '../components/Footer'; class App extends Component { render() { // Injected by connect() call: const { dispatch, visibleTodos, visibilityFilter } = this.props; return ( <div> <AddTodo onAddClick={text => dispatch(addTodo(text)) } /> <TodoList todos={this.props.visibleTodos} onTodoClick={index => dispatch(completeTodo(index)) } /> <Footer filter={visibilityFilter} onFilterChange={nextFilter => dispatch(setVisibilityFilter(nextFilter)) } /> </div> ); } } App.propTypes = { visibleTodos: PropTypes.arrayOf(PropTypes.shape({ text: PropTypes.string.isRequired, completed: PropTypes.bool.isRequired })), visibilityFilter: PropTypes.oneOf([ 'SHOW_ALL', 'SHOW_COMPLETED', 'SHOW_ACTIVE' ]).isRequired }; function selectTodos(todos, filter) { switch (filter) { case VisibilityFilters.SHOW_ALL: return todos; case VisibilityFilters.SHOW_COMPLETED: return todos.filter(todo => todo.completed); case VisibilityFilters.SHOW_ACTIVE: return todos.filter(todo => !todo.completed); } } // Which props do we want to inject, given the global state? // Note: use https://github.com/faassen/reselect for better performance. function select(state) { return { visibleTodos: selectTodos(state.todos, state.visibilityFilter), visibilityFilter: state.visibilityFilter }; } // Wrap the component to inject dispatch and state into it export default connect(select)(App); ~~~ ### 笨拙组件 #### `components/AddTodo.js` ~~~ import React, { findDOMNode, Component, PropTypes } from 'react'; export default class AddTodo extends Component { render() { return ( <div> <input type='text' ref='input' /> <button onClick={(e) => this.handleClick(e)}> Add </button> </div> ); } handleClick(e) { const node = findDOMNode(this.refs.input); const text = node.value.trim(); this.props.onAddClick(text); node.value = ''; } } AddTodo.propTypes = { onAddClick: PropTypes.func.isRequired }; ~~~ #### `components/Footer.js` ~~~ import React, { Component, PropTypes } from 'react'; export default class Footer extends Component { renderFilter(filter, name) { if (filter === this.props.filter) { return name; } return ( <a href='#' onClick={e => { e.preventDefault(); this.props.onFilterChange(filter); }}> {name} </a> ); } render() { return ( <p> Show: {' '} {this.renderFilter('SHOW_ALL', 'All')} {', '} {this.renderFilter('SHOW_COMPLETED', 'Completed')} {', '} {this.renderFilter('SHOW_ACTIVE', 'Active')} . </p> ); } } Footer.propTypes = { onFilterChange: PropTypes.func.isRequired, filter: PropTypes.oneOf([ 'SHOW_ALL', 'SHOW_COMPLETED', 'SHOW_ACTIVE' ]).isRequired }; ~~~ #### `components/Todo.js` ~~~ import React, { Component, PropTypes } from 'react'; export default class Todo extends Component { render() { return ( <li onClick={this.props.onClick} style={{ textDecoration: this.props.completed ? 'line-through' : 'none', cursor: this.props.completed ? 'default' : 'pointer' }}> {this.props.text} </li> ); } } Todo.propTypes = { onClick: PropTypes.func.isRequired, text: PropTypes.string.isRequired, completed: PropTypes.bool.isRequired }; ~~~ #### `components/TodoList.js` ~~~ import React, { Component, PropTypes } from 'react'; import Todo from './Todo'; export default class TodoList extends Component { render() { return ( <ul> {this.props.todos.map((todo, index) => <Todo {...todo} key={index} onClick={() => this.props.onTodoClick(index)} /> )} </ul> ); } } TodoList.propTypes = { onTodoClick: PropTypes.func.isRequired, todos: PropTypes.arrayOf(PropTypes.shape({ text: PropTypes.string.isRequired, completed: PropTypes.bool.isRequired }).isRequired).isRequired }; ~~~