示例: Todo 列表

这是我们在基础教程里开发的迷你型的任务管理应用的完整源码。

入口文件

index.js

  1. import React from 'react'
  2. import { render } from 'react-dom'
  3. import { Provider } from 'react-redux'
  4. import { createStore } from 'redux'
  5. import todoApp from './reducers'
  6. import App from './components/App'
  7. let store = createStore(todoApp)
  8. render(
  9. <Provider store={store}>
  10. <App />
  11. </Provider>,
  12. document.getElementById('root')
  13. )

创建 Action

actions/index.js

  1. let nextTodoId = 0
  2. export const addTodo = text => {
  3. return {
  4. type: 'ADD_TODO',
  5. id: nextTodoId++,
  6. text
  7. }
  8. }
  9. export const setVisibilityFilter = filter => {
  10. return {
  11. type: 'SET_VISIBILITY_FILTER',
  12. filter
  13. }
  14. }
  15. export const toggleTodo = id => {
  16. return {
  17. type: 'TOGGLE_TODO',
  18. id
  19. }
  20. }

Reducers

reducers/todos.js

  1. const todos = (state = [], action) => {
  2. switch (action.type) {
  3. case 'ADD_TODO':
  4. return [
  5. ...state,
  6. {
  7. id: action.id,
  8. text: action.text,
  9. completed: false
  10. }
  11. ]
  12. case 'TOGGLE_TODO':
  13. return state.map(todo =>
  14. (todo.id === action.id)
  15. ? {...todo, completed: !todo.completed}
  16. : todo
  17. )
  18. default:
  19. return state
  20. }
  21. }
  22. export default todos

reducers/visibilityFilter.js

  1. const visibilityFilter = (state = 'SHOW_ALL', action) => {
  2. switch (action.type) {
  3. case 'SET_VISIBILITY_FILTER':
  4. return action.filter
  5. default:
  6. return state
  7. }
  8. }
  9. export default visibilityFilter

reducers/index.js

  1. import { combineReducers } from 'redux'
  2. import todos from './todos'
  3. import visibilityFilter from './visibilityFilter'
  4. const todoApp = combineReducers({
  5. todos,
  6. visibilityFilter
  7. })
  8. export default todoApp

展示组件

components/Todo.js

  1. import React from 'react'
  2. import PropTypes from 'prop-types'
  3. const Todo = ({ onClick, completed, text }) => (
  4. <li
  5. onClick={onClick}
  6. style={ {
  7. textDecoration: completed ? 'line-through' : 'none'
  8. }}
  9. >
  10. {text}
  11. </li>
  12. )
  13. Todo.propTypes = {
  14. onClick: PropTypes.func.isRequired,
  15. completed: PropTypes.bool.isRequired,
  16. text: PropTypes.string.isRequired
  17. }
  18. export default Todo

components/TodoList.js

  1. import React from 'react'
  2. import PropTypes from 'prop-types'
  3. import Todo from './Todo'
  4. const TodoList = ({ todos, onTodoClick }) => (
  5. <ul>
  6. {todos.map(todo => (
  7. <Todo key={todo.id} {...todo} onClick={() => onTodoClick(todo.id)} />
  8. ))}
  9. </ul>
  10. )
  11. TodoList.propTypes = {
  12. todos: PropTypes.arrayOf(
  13. PropTypes.shape({
  14. id: PropTypes.number.isRequired,
  15. completed: PropTypes.bool.isRequired,
  16. text: PropTypes.string.isRequired
  17. }).isRequired
  18. ).isRequired,
  19. onTodoClick: PropTypes.func.isRequired
  20. }
  21. export default TodoList
  1. import React from 'react'
  2. import PropTypes from 'prop-types'
  3. const Link = ({ active, children, onClick }) => {
  4. if (active) {
  5. return <span>{children}</span>
  6. }
  7. return (
  8. <a
  9. href=""
  10. onClick={e => {
  11. e.preventDefault()
  12. onClick()
  13. }}
  14. >
  15. {children}
  16. </a>
  17. )
  18. }
  19. Link.propTypes = {
  20. active: PropTypes.bool.isRequired,
  21. children: PropTypes.node.isRequired,
  22. onClick: PropTypes.func.isRequired
  23. }
  24. export default Link
  1. import React from 'react'
  2. import FilterLink from '../containers/FilterLink'
  3. const Footer = () => (
  4. <p>
  5. Show:
  6. {' '}
  7. <FilterLink filter="SHOW_ALL">
  8. All
  9. </FilterLink>
  10. {', '}
  11. <FilterLink filter="SHOW_ACTIVE">
  12. Active
  13. </FilterLink>
  14. {', '}
  15. <FilterLink filter="SHOW_COMPLETED">
  16. Completed
  17. </FilterLink>
  18. </p>
  19. )
  20. export default Footer

components/App.js

  1. import React from 'react'
  2. import Footer from './Footer'
  3. import AddTodo from '../containers/AddTodo'
  4. import VisibleTodoList from '../containers/VisibleTodoList'
  5. const App = () => (
  6. <div>
  7. <AddTodo />
  8. <VisibleTodoList />
  9. <Footer />
  10. </div>
  11. )
  12. export default App

容器组件

containers/VisibleTodoList.js

  1. import { connect } from 'react-redux'
  2. import { toggleTodo } from '../actions'
  3. import TodoList from '../components/TodoList'
  4. const getVisibleTodos = (todos, filter) => {
  5. switch (filter) {
  6. case 'SHOW_COMPLETED':
  7. return todos.filter(t => t.completed)
  8. case 'SHOW_ACTIVE':
  9. return todos.filter(t => !t.completed)
  10. case 'SHOW_ALL':
  11. default:
  12. return todos
  13. }
  14. }
  15. const mapStateToProps = state => {
  16. return {
  17. todos: getVisibleTodos(state.todos, state.visibilityFilter)
  18. }
  19. }
  20. const mapDispatchToProps = dispatch => {
  21. return {
  22. onTodoClick: id => {
  23. dispatch(toggleTodo(id))
  24. }
  25. }
  26. }
  27. const VisibleTodoList = connect(
  28. mapStateToProps,
  29. mapDispatchToProps
  30. )(TodoList)
  31. export default VisibleTodoList
  1. import { connect } from 'react-redux'
  2. import { setVisibilityFilter } from '../actions'
  3. import Link from '../components/Link'
  4. const mapStateToProps = (state, ownProps) => {
  5. return {
  6. active: ownProps.filter === state.visibilityFilter
  7. }
  8. }
  9. const mapDispatchToProps = (dispatch, ownProps) => {
  10. return {
  11. onClick: () => {
  12. dispatch(setVisibilityFilter(ownProps.filter))
  13. }
  14. }
  15. }
  16. const FilterLink = connect(
  17. mapStateToProps,
  18. mapDispatchToProps
  19. )(Link)
  20. export default FilterLink

其他组件

containers/AddTodo.js

  1. import React from 'react'
  2. import { connect } from 'react-redux'
  3. import { addTodo } from '../actions'
  4. let AddTodo = ({ dispatch }) => {
  5. let input
  6. return (
  7. <div>
  8. <form
  9. onSubmit={e => {
  10. e.preventDefault()
  11. if (!input.value.trim()) {
  12. return
  13. }
  14. dispatch(addTodo(input.value))
  15. input.value = ''
  16. }}
  17. >
  18. <input
  19. ref={node => {
  20. input = node
  21. }}
  22. />
  23. <button type="submit">
  24. Add Todo
  25. </button>
  26. </form>
  27. </div>
  28. )
  29. }
  30. AddTodo = connect()(AddTodo)
  31. export default AddTodo