書籍完整目錄html
上一節講完了 redux 中的概念,可是仍然沒有和 react 聯繫起來,這一節
將利用 redux 在 react 中實現完整的 todolist:node
在 react 使用 reduxreact
經過 Provider 鏈接 react 和 redux storeshell
建立 action creatorsnpm
建立 reducerredux
建立 Container Componentsegmentfault
牀架 Dummy Component數據結構
redux 能夠和不少第三方的框架結合起來使用,爲了在 react 中使用 redux,能夠經過 react-redux
框架
安裝 react-redux
dom
$ npm install --save react-redux
react-redux 提供了一個叫 Provider 的組件,將 react 和 react-redux 結合的方式是用 Provider 嵌套應用的 App 組件,並將 redux store 做爲屬性傳遞到 Provider 組件之中。
index.js
import React from 'react' import { render } from 'react-dom' import { Provider } from 'react-redux' import { createStore } from 'redux' import todoApp from './reducers' import App from './components/App' /** store 數據結構 sample { visibilityFilter: 'SHOW_ALL', todos: [ { text: 'Consider using Redux', completed: true, }, { text: 'Keep all state in a single tree', completed: false } ] } */ let store = createStore(todoApp) render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )
這裏使用到了 React 的 Context ,App 下面的全部組件能夠利用 context 獲取傳入到 Provider 中的 store
actions/index.js
let nextTodoId = 0 export const addTodo = (text) => { return { type: 'ADD_TODO', id: nextTodoId++, text } } export const setVisibilityFilter = (filter) => { return { type: 'SET_VISIBILITY_FILTER', filter } } export const toggleTodo = (id) => { return { type: 'TOGGLE_TODO', id } }
首先建立根 reducer ,經過 redux.combineReducers
方法將其餘 reducer 結合起來,每一個數據 key 都須要實現一個對應的 reducer
reducer/index.js
import { combineReducers } from 'redux' import todos from './todos' import visibilityFilter from './visibilityFilter' const todoApp = combineReducers({ todos, visibilityFilter }) export default todoApp
接着是 todos reducer , 須要注意的地方是其中使用 Object.assign 方法保證每次都是返回新的
對象
reducer/todos.js
const todo = (state, action) => { switch (action.type) { case 'ADD_TODO': return { id: action.id, text: action.text, completed: false } case 'TOGGLE_TODO': if (state.id !== action.id) { return state } return Object.assign({}, state, { completed: !state.completed }) default: return state } } const todos = (state = [], action) => { switch (action.type) { case 'ADD_TODO': return [ ...state, todo(undefined, action) ] case 'TOGGLE_TODO': return state.map(t => todo(t, action) ) default: return state } } export default todos
最後是 visibilityFilter
reducers/visibibityFilter.js
const visibilityFilter = (state = 'SHOW_ALL', action) => { switch (action.type) { case 'SET_VISIBILITY_FILTER': return action.filter default: return state } } export default visibilityFilter
在介紹 flux 的時候介紹過組件分兩個類型,smart component 和 dummy component,在 redux 中 Container Component 就是 smart component
在 react 應用中,store 的數據只有 container component 能知曉,container component 會將知曉的數據傳遞給 dummy components ,除此以外 action 的觸發方法也會由它傳遞給 dummy components
react-redux 提供了一個叫 connect 的方法,能夠將一個組件變爲 container component
const ContainerComponent = connect( /** * 方法將 store 做爲參數,返回有個 {key: Value} 對象,key 做爲屬性傳遞給 DummyComponent * @type {[type]} */ mapStateToProps: Function, /** * 方法傳遞 store.dispatch 做爲參數,返回一個{key: Function} 對象,key 做爲屬性傳遞給 DummyComponent * @type {[type]} */ mapDispatchToProps: Function )(DummyComponent)
其本質就是獲取react context 中的 store,並將 store 中的數據做爲屬性傳遞到原來的組件中
todolist 會分爲三個 container,有個負責 todolist,一個負責 filter,一個爲添加 todo,首先是 todolist。
containers/VisibleTodoList.js
import { connect } from 'react-redux' import { toggleTodo } from '../actions' import TodoList from '../components/TodoList' const getVisibleTodos = (todos, filter) => { switch (filter) { case 'SHOW_ALL': return todos case 'SHOW_COMPLETED': return todos.filter(t => t.completed) case 'SHOW_ACTIVE': return todos.filter(t => !t.completed) } } const mapStateToProps = (state) => { return { todos: getVisibleTodos(state.todos, state.visibilityFilter) } } const mapDispatchToProps = (dispatch) => { return { onTodoClick: (id) => { dispatch(toggleTodo(id)) } } } const VisibleTodoList = connect( mapStateToProps, mapDispatchToProps )(TodoList) export default VisibleTodoList
containers/FilterLink.js
import { connect } from 'react-redux' import { setVisibilityFilter } from '../actions' import Link from '../components/Link' const mapStateToProps = (state, ownProps) => { return { active: ownProps.filter === state.visibilityFilter } } const mapDispatchToProps = (dispatch, ownProps) => { return { onClick: () => { dispatch(setVisibilityFilter(ownProps.filter)) } } } const FilterLink = connect( mapStateToProps, mapDispatchToProps )(Link) export default FilterLink
containers/AddTodo.js
import React from 'react' import { connect } from 'react-redux' import { addTodo } from '../actions' let AddTodo = ({ dispatch }) => { let input return ( <div> <form onSubmit={e => { e.preventDefault() if (!input.value.trim()) { return } dispatch(addTodo(input.value)) input.value = '' }}> <input ref={node => { input = node }} /> <button type="submit"> Add Todo </button> </form> </div> ) } AddTodo = connect()(AddTodo) export default AddTodo
components/App.js: App.js 鏈接全部的 Container Components
import React from 'react' import Footer from './Footer' import AddTodo from '../containers/AddTodo' import VisibleTodoList from '../containers/VisibleTodoList' const App = () => ( <div> <AddTodo /> <VisibleTodoList /> <Footer /> </div> ) export default App
components/TodoList.js: 展示 todos 列表
import React, { PropTypes } from 'react' import Todo from './Todo' const TodoList = ({ todos, onTodoClick }) => ( <ul> {todos.map(todo => <Todo key={todo.id} {...todo} onClick={() => onTodoClick(todo.id)} /> )} </ul> ) TodoList.propTypes = { todos: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.number.isRequired, completed: PropTypes.bool.isRequired, text: PropTypes.string.isRequired }).isRequired).isRequired, onTodoClick: PropTypes.func.isRequired } export default TodoList
components/Todo.js
import React, { PropTypes } from 'react' const Todo = ({ onClick, completed, text }) => ( <li onClick={onClick} style={{ textDecoration: completed ? 'line-through' : 'none' }} > {text} </li> ) Todo.propTypes = { onClick: PropTypes.func.isRequired, completed: PropTypes.bool.isRequired, text: PropTypes.string.isRequired } export default Todo
components/Link.js
import React, { PropTypes } from 'react' const Link = ({ active, children, onClick }) => { if (active) { return <span>{children}</span> } return ( <a href="#" onClick={e => { e.preventDefault() onClick() }} > {children} </a> ) } Link.propTypes = { active: PropTypes.bool.isRequired, children: PropTypes.node.isRequired, onClick: PropTypes.func.isRequired } export default Link
components/Footer.js
import React from 'react' import FilterLink from '../containers/FilterLink' const Footer = () => ( <p> Show: {" "} <FilterLink filter="SHOW_ALL"> All </FilterLink> {", "} <FilterLink filter="SHOW_ACTIVE"> Active </FilterLink> {", "} <FilterLink filter="SHOW_COMPLETED"> Completed </FilterLink> </p> ) export default Footer