### redux简介
- redux是一个配合react视图层框架使用的数据层框架
- 方便大型react项目之中的复杂组件传值
- 耦合性高的数据使用redux管理
- redux中包含 组件,store,reducer
- 1. store必须是唯一的,整个项目之中只能有一个数据存储空间
- 2. store中的数据是store自己更新的,并不是reducer,这也是为什么reducer中不能直接改变state中的数据
- 3. Reducer必须是个纯函数。
- 3.1 纯函数: 是给定固定的输入,就一定会有固定的输出。而且不能有任何的副作用(不能对参数进行修改)
#### redux数据流向
- store就像一个图书管理员
- 图书管理员会给每个需要借书的人发一个通讯工具(store)
- 通讯工具store有一个方法叫做subscribe(),每当图书馆的图书有变化,这个方法就会自动执行
- 通讯工具store提供一个getState()方法,方便借书人立马得到最新的图书馆数据,配合subscribe()使用
- 通讯工具store提供一个dispatch()方法,方便借书人传达他想借阅的书籍名称
- reducer是图书管理员的查询手册
- 他是图书管理员的查询手册,当图书管理员接到借书人的消息后,他会查阅reducer
- 图书馆管理员也是通过查询手册确定数据的更新
- 查询手册返回的是一个方法,这个方法有2个参数(state,action)
- state就是图书馆数据,action是借书人通过store传递过来的参数,也就是书名,通过action,查询手册才能查询到数据
- reducer返回的方法不能直接更改state
- 组件就像借书人
- 借书人需要借书,通过图书管理员提供的通讯工具store提供的dispatch方法,传达他要借的书(action)
- 借书人通过图书管理员提供的通讯工具store提供的subscribe()和getState()获取图书管的最新咨询
### 创建项目
- create-react-app todo-list
- 注意项目名称不能有大写字母
### 删除不必要文件
- src目录中的:App.css, App.test.js, logo.svg, serviceWorker.js文件
- public目录中的: manifest.json文件
### 安装依赖
- yarn add antd
- yarn add redux
### 入口index.js编写
1 import React from 'react'; 2 import ReactDOM from 'react-dom'; 3 import App from './App'; // 引入万年老二组件 4 5 ReactDOM.render(<App />, document.getElementById('root'));
### 创建redux的store
- index.js
1 /** 2 * store就像一个图书管理员,在接到组件(借书人)派发的 dispatch(借书人说的话) 时, 3 * 他本身不知道书在什么位置,有没有这本书,需要查询 reducer (图书列表) 4 */ 5 import { createStore } from 'redux' 6 import todoListReducer from './reducer' // 引入图书列表 7 8 9 const store = createStore(todoListReducer) // 查询图书列表 10 11 12 export default store
- reducer.js
1 /** 2 * reducer 相当于图书管理员 store 的查询手册, 3 * 通过查询手册,确认组件 借书人人需要的书在什么地方。 4 */ 5 const todoState = { 6 inputValue : "", 7 list: [] 8 } 9 10 export default (state=todoState, action) => { 11 if ( action.type === 'change_input_value'){ // 确认书名, 执行动作 12 const newState = JSON.parse(JSON.stringify(state)) 13 newState.inputValue = action.value 14 console.log(newState) 15 return newState 16 } 17 if ( action.type === 'change_list_value'){ // 确认书名, 执行动作 18 const newState = JSON.parse(JSON.stringify(state)) 19 newState.list = [...state.list, action.item] 20 newState.inputValue = '' 21 console.log(newState.list) 22 return newState 23 } 24 return state 25 }
### 编写html结构
- 在App.js中引入所需依赖
- 1. antd的样式
- 2. antd的组件
- 3. react
- 4. store
1 /** 2 * 组件就是一个需要借书的人,通过 dispatch 传达 action (书名)给图书管理员(store) 3 */ 4 /** 5 * 组件就是一个需要借书的人,通过 dispatch 传达 action (书名)给图书管理员(store) 6 */ 7 8 /** 9 * 组件就是一个需要借书的人,通过 dispatch 传达 action (书名)给图书管理员(store) 10 */ 11 12 import React, { Component, Fragment }from 'react'; 13 import { Input, Button, List, message } from "antd"; 14 import store from './store'; // 引入图书管理员 store 15 import "antd/dist/antd.css"; 16 class App extends Component { 17 constructor(props){ 18 super(props) 19 this.state = store.getState() 20 console.log(store.getState()) 21 this.handleInputChange = this.handleInputChange.bind(this); 22 this.addTodoList = this.addTodoList.bind(this); 23 this.handleStroeChange = this.handleStroeChange.bind(this); 24 // this.deletTodoList = this.deletTodoList.bind(this); 25 store.subscribe(this.handleStroeChange) // 图书管理员会随时通知各个借书人,图书馆书籍的变化 26 } 27 28 render() { 29 return ( 30 <Fragment> 31 <div style={{ marginTop: '10px', marginLeft: '10px'}}> 32 <Input 33 placeholder='todo-list' 34 style={{width: '300px', marginRight: '10px'}} 35 onChange = { this.handleInputChange } 36 value = { this.state.inputValue } 37 /> 38 <Button 39 type="primary" 40 onClick = { this.addTodoList } 41 >提交</Button> 42 </div> 43 <List 44 style={{width: '300px', marginLeft: '10px', marginTop: '5px'}} 45 size="large" 46 bordered 47 dataSource={ this.state.list ? this.state.list : null } 48 renderItem={ (item, index) => <List.Item style={{position:'relative'}}> 49 {item} 50 <Button 51 type='danger' 52 style={{position: 'absolute', right: '10px', top:'50%', marginTop:'-5%'}} 53 onClick={ this.deletTodoList.bind(this, index) } 54 >删除</Button> 55 </List.Item>} 56 /> 57 </Fragment> 58 ); 59 } 60 handleInputChange(e) { 61 const action = { 62 type: 'change_input_value', // 借什么书 63 value: e.target.value 64 } 65 store.dispatch(action); // 传达给store 66 console.log(e.target.value) 67 } 68 addTodoList() { 69 if (this.state.inputValue) { 70 const action = { 71 type: 'change_list_value', 72 item: this.state.inputValue 73 } 74 store.dispatch(action) 75 } else { 76 message.warning('请输入内容'); 77 } 78 } 79 deletTodoList(index) { 80 const action = { 81 type: 'delet_list_value', 82 value: index 83 } 84 store.dispatch(action) 85 } 86 handleStroeChange() { 87 this.setState(store.getState()) // 每当图书馆有变化的时候,图书管理员(store)通过这个方式告诉借书人(组件) 88 } 89 } 90 91 export default App;
### ActionType的拆分
- 我们在组件中创建action的时候,配置type等于一个字符串,在reducer中判断action.type的时候,容易出错,并且出错也不会报错。
- 所以我们需要将ActionType做一个拆分
- 在store中新建一个actionTypes.js文件
1 export const CHANGE_INPUT_VALUE = 'change_input_value' 2 export const CHANGE_LIST_VALUE = 'change_list_value' 3 export const DELETE_LIST_VALUE = 'delet_list_value'
- 在需要使用的组件中,以及reducer中导入替换到原来的字符串
1 /** 2 * reducer 相当于图书管理员 store 的查询手册, 3 * 通过查询手册,确认组件 借书人人需要的书在什么地方。 4 */ 5 6 import { CHANGE_INPUT_VALUE, CHANGE_LIST_VALUE, DELETE_LIST_VALUE } from './actionTypes' 7 const todoState = { 8 inputValue : "", 9 list: [] 10 } 11 12 export default (state=todoState, action) => { 13 if ( action.type === CHANGE_INPUT_VALUE){ // 确认书名, 执行动作 14 const newState = JSON.parse(JSON.stringify(state)) 15 newState.inputValue = action.value 16 console.log(newState) 17 return newState 18 } 19 if ( action.type === CHANGE_LIST_VALUE){ // 确认书名, 执行动作 20 const newState = JSON.parse(JSON.stringify(state)) 21 newState.list = [...state.list, action.item] 22 newState.inputValue = '' 23 console.log(newState.list) 24 return newState 25 } 26 if ( action.type === DELETE_LIST_VALUE){ 27 const newState = JSON.parse(JSON.stringify(state)) 28 console.log(action.value) 29 newState.list.splice(action.value, 1) 30 return newState 31 } 32 return state 33 } 34 35 36 // --------------------------------分割线--------------------------------- 37 38 39 /** 40 * 组件就是一个需要借书的人,通过 dispatch 传达 action (书名)给图书管理员(store) 41 */ 42 43 import React, { Component, Fragment }from 'react'; 44 import { Input, Button, List, message } from "antd"; 45 import store from './store'; // 引入图书管理员 store 46 import { CHANGE_INPUT_VALUE, CHANGE_LIST_VALUE, DELETE_LIST_VALUE } from './store/actionTypes' 47 import "antd/dist/antd.css"; 48 class App extends Component { 49 constructor(props){ 50 super(props) 51 this.state = store.getState() 52 console.log(store.getState()) 53 this.handleInputChange = this.handleInputChange.bind(this); 54 this.addTodoList = this.addTodoList.bind(this); 55 this.handleStroeChange = this.handleStroeChange.bind(this); 56 // this.deletTodoList = this.deletTodoList.bind(this); 57 store.subscribe(this.handleStroeChange) // 图书管理员会随时通知各个借书人,图书馆书籍的变化 58 } 59 60 render() { 61 return ( 62 <Fragment> 63 <div style={{ marginTop: '10px', marginLeft: '10px'}}> 64 <Input 65 placeholder='todo-list' 66 style={{width: '300px', marginRight: '10px'}} 67 onChange = { this.handleInputChange } 68 value = { this.state.inputValue } 69 /> 70 <Button 71 type="primary" 72 onClick = { this.addTodoList } 73 >提交</Button> 74 </div> 75 <List 76 style={{width: '300px', marginLeft: '10px', marginTop: '5px'}} 77 size="large" 78 bordered 79 dataSource={ this.state.list ? this.state.list : null } 80 renderItem={ (item, index) => <List.Item style={{position:'relative'}}> 81 {item} 82 <Button 83 type='danger' 84 style={{position: 'absolute', right: '10px', top:'50%', marginTop:'-5%'}} 85 onClick={ this.deletTodoList.bind(this, index) } 86 >删除</Button> 87 </List.Item>} 88 /> 89 </Fragment> 90 ); 91 } 92 handleInputChange(e) { 93 const action = { 94 type: CHANGE_INPUT_VALUE, // 借什么书 95 value: e.target.value 96 } 97 store.dispatch(action); // 传达给store 98 console.log(e.target.value) 99 } 100 addTodoList() { 101 if (this.state.inputValue) { 102 const action = { 103 type: CHANGE_LIST_VALUE, 104 item: this.state.inputValue 105 } 106 store.dispatch(action) 107 } else { 108 message.warning('请输入内容'); 109 } 110 } 111 deletTodoList(index) { 112 const action = { 113 type: DELETE_LIST_VALUE, 114 value: index 115 } 116 store.dispatch(action) 117 } 118 handleStroeChange() { 119 this.setState(store.getState()) // 每当图书馆有变化的时候,图书管理员(store)通过这个方式告诉借书人(组件) 120 } 121 } 122 123 export default App;
###使用actionCreators统一创建action
- 之前我们创建的action,都是在组件中创建的,但是如果是大型的,逻辑复杂的项目这样写不方便前端测试,也不利于维护
- 因此,我们需要将action统一在一个地方创建
- 在store文件夹中创建一个actionCreators.js,专门用来创建action
- 并且,要在actionCreators中引入我们之前actionTypes.js。
- 然后在需要使用action的组件按需求引入即可
1 /** 2 * 其实就是返回一个能获取action的方法 3 * actionCreators.js 4 */ 5 import { CHANGE_INPUT_VALUE, CHANGE_LIST_VALUE, DELETE_LIST_VALUE } from './actionTypes' 6 7 export const getInputChangeValue = (value) => ({ 8 type: CHANGE_INPUT_VALUE, 9 value 10 }) 11 12 export const getAddTodoListValue = (item) => ({ 13 type: CHANGE_LIST_VALUE, 14 item 15 }) 16 17 export const getDeletTodoListValue = (index) => ({ 18 type: DELETE_LIST_VALUE, 19 index 20 }) 21 22 // -----------------分割线-------------------------- 23 24 /** 25 * App.js 26 * 在组件中引用action 27 * 此处省略了无关代码,可以参照上面的代码 28 */ 29 /** 30 * 组件就是一个需要借书的人,通过 dispatch 传达 action (书名)给图书管理员(store) 31 */ 32 33 import React, { Component, Fragment }from 'react'; 34 import { Input, Button, List, message } from "antd"; 35 import store from './store'; // 引入图书管理员 store 36 // 引入action 37 import { getInputChangeValue, getAddTodoListValue, getDeletTodoListValue } from './store/actionCreators' 38 // import { CHANGE_INPUT_VALUE, CHANGE_LIST_VALUE, DELETE_LIST_VALUE } from './store/actionTypes' 39 import "antd/dist/antd.css"; 40 class App extends Component { 41 constructor(props){ 42 super(props) 43 this.state = store.getState() 44 console.log(store.getState()) 45 this.handleInputChange = this.handleInputChange.bind(this); 46 this.addTodoList = this.addTodoList.bind(this); 47 this.handleStroeChange = this.handleStroeChange.bind(this); 48 // this.deletTodoList = this.deletTodoList.bind(this); 49 store.subscribe(this.handleStroeChange) // 图书管理员会随时通知各个借书人,图书馆书籍的变化 50 } 51 52 render() { 53 return ( 54 <Fragment> 55 <div style={{ marginTop: '10px', marginLeft: '10px'}}> 56 <Input 57 placeholder='todo-list' 58 style={{width: '300px', marginRight: '10px'}} 59 onChange = { this.handleInputChange } 60 value = { this.state.inputValue } 61 /> 62 <Button 63 type="primary" 64 onClick = { this.addTodoList } 65 >提交</Button> 66 </div> 67 <List 68 style={{width: '300px', marginLeft: '10px', marginTop: '5px'}} 69 size="large" 70 bordered 71 dataSource={ this.state.list ? this.state.list : null } 72 renderItem={ (item, index) => <List.Item style={{position:'relative'}}> 73 {item} 74 <Button 75 type='danger' 76 style={{position: 'absolute', right: '10px', top:'50%', marginTop:'-5%'}} 77 onClick={ this.deletTodoList.bind(this, index) } 78 >删除</Button> 79 </List.Item>} 80 /> 81 </Fragment> 82 ); 83 } 84 handleInputChange(e) { 85 /* 86 const action = { 87 type: CHANGE_INPUT_VALUE, // 借什么书 88 value: e.target.value 89 } 90 */ 91 const action = getInputChangeValue(e.target.value) 92 store.dispatch(action); // 传达给store 93 console.log(e.target.value) 94 } 95 // 添加 96 addTodoList() { 97 /* 98 if (this.state.inputValue) { 99 const action = { 100 type: CHANGE_LIST_VALUE, 101 item: this.state.inputValue 102 } 103 store.dispatch(action) 104 } else { 105 message.warning('请输入内容'); 106 } 107 */ 108 if (this.state.inputValue) { 109 const action = getAddTodoListValue(this.state.inputValue) 110 store.dispatch(action) 111 } else { 112 message.warning('请输入内容'); 113 } 114 } 115 // 删除 116 deletTodoList(index) { 117 /* 118 const action = { 119 type: DELETE_LIST_VALUE, 120 value: index 121 } 122 */ 123 const action = getDeletTodoListValue(index) 124 store.dispatch(action) 125 } 126 handleStroeChange() { 127 this.setState(store.getState()) // 每当图书馆有变化的时候,图书管理员(store)通过这个方式告诉借书人(组件) 128 } 129 } 130 131 export default App;