一. Redux 概念简述
React 本身是一个视图层的框架,我们想要开发比较大型的项目,还需要 Redux 这个数据层的框架来帮助我们。下图就是是否使用了Redux 进行传值的比较。
Redux: 把组件所有的数据都放到公用的存储空间 Store 里,当绿色组件想要改变数据传递给其它组件的时候,只需要改变 Store 里面对应的数据即可,不需要一层一层地去传值。灰色组件会自动去感知到 Store 里面数据的变化,然后重新去获取数据。
Flux:最原始的官方提供的辅助 React 开发的数据层框架,但是在使用时会存在一些缺点,它的公共存储区域 Stroe 里面可以有很多个 Store ,这就会产生互相依赖的问题。Redux 就是 Flux 的升级,添加了 Reducer。
二. Redux 的工作流程
Store:存储数据的公共区域 ;(理解成图书馆管理员)
React Components :每一个 React 组件 ;(视为借书的人)
Action Creators :(当我在图书馆借书的时候,我要和管理员说借什么书,那么这个语句的表达和传递就可以理解为 Action Creators );
Reducers :(记录图书的记录本);
实际理解:
查找过程:首先我有一个组件,这个组件要去获取 Store 中的一些数据,然后就要告诉 Store 我要获取数据,这句话就是 Action Creators ,Store 接收到了这句话以后,但是它不知道你应该给你什么样的数据,它就要去 Reducers 中查,Reducers 会告诉它应该给怎么样的数据,Store 知道了以后把对应的数据给组件即可。
修改过程:组件先要告诉 Store ,我要改变什么数据,但是它不知道怎么去改,他就要去问 Reducers ,Reducers 就会告诉 Store 如何去改变。
三. 使用 Antd 实现 TodoList 页面布局
- 首先安装 Antd :
npm install antd --save
- 然后根据文档引入使用: TodoList.js :
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
const data = [
'Racing car sprays burning fuel into crowd.',
'Japanese princess to wed commoner.',
'Australian walks 100km after outback crash.',
'Man charged over missing wedding girl.',
'Los Angeles battles huge wildfires.',
];
class TodoList extends Component {
render() {
return (
<div style={{marginTop: '10px', marginLeft: '10px'}}>
<div>
<Input
placeholder="todo info"
style={{width: '300px', marginRight: '10px'}}
/>
<Button type="primary">提交</Button>
</div>
<List
style={{marginTop: '10px', width: '300px'}}
bordered
dataSource={data}
renderItem={item => <List.Item>{item}</List.Item>}
/>
</div>
)
}
}
export default TodoList;
四. 创建 redux 中的 store
- 首先安装 Redux :
npm install --save redux
- 项目目录结构:
- store 下的 index.js :
/*创建一个store*/
import { createStore } from 'redux'; //引入createStore
import reducer from './reducer';
const store = createStore(reducer); //创建一个store
export default store;
- reducer.js :
/*建立一个reducer*/
const defaultState = {
inputValue: '123',
list: [1, 2]
};
export default (state = defaultState, action) => {
//state存放的就是所有数据的信息,
return state;
}
- TodoList.js :
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
import store from './store/index'; //引入store
class TodoList extends Component {
constructor(props) {
super(props);
this.state = store.getState();
console.log(this.state); //{inputValue: "123", list: Array(2)}
}
render() {
return (
<div style={{marginTop: '10px', marginLeft: '10px'}}>
<div>
<Input
placeholder="todo info"
style={{width: '300px', marginRight: '10px'}}
value = {this.state.inputValue}
/>
<Button type="primary">提交</Button>
</div>
<List
style={{marginTop: '10px', width: '300px'}}
bordered
dataSource={this.state.list}
renderItem={item => <List.Item>{item}</List.Item>}
/>
</div>
)
}
}
export default TodoList;
- 页面效果:
五. Action 和 Reducer 的编写
- store 下面的 index.js :
/*创建一个store*/
import { createStore } from 'redux'; //引入createStore
import reducer from './reducer';
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() //配置redux devtools
); //创建一个store
export default store;
- reducer.js :
/*建立一个reducer*/
const defaultState = {
inputValue: '',
list: [1, 2]
};
//reducer可以接收state,但是绝不可以修改state
export default (state = defaultState, action) => {
//state上一次存储的数据,action就是组件通过store传过来
if( action.type === 'change_input_value' ) {
const newState = JSON.parse(JSON.stringify(state)); //深拷贝
newState.inputValue = action.value;
return newState; //返回给store,store就会拿新的数据替换掉老的数据
}
if( action.type === 'add_todo_item' ) {
const newState = JSON.parse(JSON.stringify(state));
newState.list.push(newState.inputValue);
newState.inputValue = '';
return newState;
}
return state;
}
- TodoList.js :
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
import store from './store/index'; //引入store
class TodoList extends Component {
constructor(props) {
super(props);
this.state = store.getState();
this.handleInputChange = this.handleInputChange.bind(this);
this.handleStoreChange = this.handleStoreChange.bind(this);
this.handleBtnClick=this.handleBtnClick.bind(this);
store.subscribe(this.handleStoreChange); //只要store发生改变就执行handleStoreChange这个函数
}
render() {
return (
<div style={{marginTop: '10px', marginLeft: '10px'}}>
<div>
<Input
placeholder="todo info"
style={{width: '300px', marginRight: '10px'}}
value = {this.state.inputValue}
onChange={this.handleInputChange}
/>
<Button
type="primary"
onClick={this.handleBtnClick}
>
提交
</Button>
</div>
<List
style={{marginTop: '10px', width: '300px'}}
bordered
dataSource={this.state.list}
renderItem={item => <List.Item>{item}</List.Item>}
/>
</div>
)
}
handleInputChange(e) {
const action = {
type: 'change_input_value',
value: e.target.value
}
store.dispatch(action);
}
handleStoreChange() {
this.setState(store.getState()); //当我感知到store的数据发生变化的时候,就去调用store.getState方法,从store里重新取一次数据,然后通过this.setState替换掉当前组件的数据
}
handleBtnClick() {
const action = {
type: 'add_todo_item',
};
store.dispatch(action);
}
}
export default TodoList;
六. 使用 Redux 完成 TodoList 的删除功能
- reducer.js :
/*建立一个reducer*/
const defaultState = {
inputValue: '',
list: [1, 2]
};
//reducer可以接收state,但是绝不可以修改state
export default (state = defaultState, action) => {
//state上一次存储的数据,action就是组件通过store传过来
if( action.type === 'change_input_value' ) {
const newState = JSON.parse(JSON.stringify(state)); //深拷贝
newState.inputValue = action.value;
return newState; //返回给store,store就会拿新的数据替换掉老的数据
}
if( action.type === 'add_todo_item' ) {
const newState = JSON.parse(JSON.stringify(state));
newState.list.push(newState.inputValue);
newState.inputValue = '';
return newState;
}
if( action.type === 'delete_todo_item' ) {
const newState = JSON.parse(JSON.stringify(state));
newState.list.splice(action.index, 1);
return newState;
}
return state;
}
- TodoList.js:
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
import store from './store/index'; //引入store
class TodoList extends Component {
constructor(props) {
super(props);
this.state = store.getState();
this.handleInputChange = this.handleInputChange.bind(this);
this.handleStoreChange = this.handleStoreChange.bind(this);
this.handleBtnClick=this.handleBtnClick.bind(this);
store.subscribe(this.handleStoreChange); //只要store发生改变就执行handleStoreChange这个函数
}
render() {
return (
<div style={{marginTop: '10px', marginLeft: '10px'}}>
<div>
<Input
placeholder="todo info"
style={{width: '300px', marginRight: '10px'}}
value = {this.state.inputValue}
onChange={this.handleInputChange}
/>
<Button
type="primary"
onClick={this.handleBtnClick}
>
提交
</Button>
</div>
<List
style={{marginTop: '10px', width: '300px'}}
bordered
dataSource={this.state.list}
renderItem={
(item, index) => <List.Item onClick={this.handleDleleteItem.bind(this, index)}> {item}</List.Item>
}
/>
</div>
)
}
handleInputChange(e) {
const action = {
type: 'change_input_value',
value: e.target.value
}
store.dispatch(action);
}
handleStoreChange() {
this.setState(store.getState()); //当我感知到store的数据发生变化的时候,就去调用store.getState方法,从store里重新取一次数据,然后通过this.setState替换掉当前组件的数据
}
handleBtnClick() {
const action = {
type: 'add_todo_item',
};
store.dispatch(action);
}
handleDleleteItem(index) {
const action = {
type: 'delete_todo_item',
index
}
store.dispatch(action);
}
}
export default TodoList;
七. ActionTypes 的拆分
action的type太多了,有时候因为拼写错误导致很长事件定位不到bug。我们可以用一个 js 文件来保存。
- 在 store 文件夹下创建一个 actionTypes.js 文件:
export const CHANGE_INPUT_VALUE = 'change_input_value';
export const ADD_TODO_ITEM = 'add_todo_item';
export const DELETE_TODO_ITEM = 'delete_todo_item';
- reducer.js :
/*建立一个reducer*/
import { CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM } from './actionTypes';
const defaultState = {
inputValue: '',
list: [1, 2]
};
//reducer可以接收state,但是绝不可以修改state
export default (state = defaultState, action) => {
//state上一次存储的数据,action就是组件通过store传过来
if( action.type === CHANGE_INPUT_VALUE ) {
const newState = JSON.parse(JSON.stringify(state)); //深拷贝
newState.inputValue = action.value;
return newState; //返回给store,store就会拿新的数据替换掉老的数据
}
if( action.type === ADD_TODO_ITEM ) {
const newState = JSON.parse(JSON.stringify(state));
newState.list.push(newState.inputValue);
newState.inputValue = '';
return newState;
}
if( action.type === DELETE_TODO_ITEM ) {
const newState = JSON.parse(JSON.stringify(state));
newState.list.splice(action.index, 1);
return newState;
}
return state;
}
同理,TodoList.js 也引入后将字符串更换成变量。
八. 使用 actionCreator 统一创建 action
一般来说都会用一个 actionCreator 来统一地去创建管理 action , 而不是在组件中管理。
- store 下面创建一个 actionCreators.js :
import { CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM } from './actionTypes';
export const getHandleInputChangeAction = (value) => ({
type: CHANGE_INPUT_VALUE,
value
})
export const getHandleBtnClick = () => ({
type: ADD_TODO_ITEM
})
export const getHandleDleleteItem = (index) => ({
type: DELETE_TODO_ITEM,
index
})
- TodoList.js :
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
import store from './store/index'; //引入store
import { getHandleInputChangeAction, getHandleBtnClick, getHandleDleleteItem } from './store/actionCreators'
class TodoList extends Component {
constructor(props) {
super(props);
this.state = store.getState();
this.handleInputChange = this.handleInputChange.bind(this);
this.handleStoreChange = this.handleStoreChange.bind(this);
this.handleBtnClick=this.handleBtnClick.bind(this);
store.subscribe(this.handleStoreChange); //只要store发生改变就执行handleStoreChange这个函数
}
render() {
return (
<div style={{marginTop: '10px', marginLeft: '10px'}}>
<div>
<Input
placeholder="todo info"
style={{width: '300px', marginRight: '10px'}}
value = {this.state.inputValue}
onChange={this.handleInputChange}
/>
<Button
type="primary"
onClick={this.handleBtnClick}
>
提交
</Button>
</div>
<List
style={{marginTop: '10px', width: '300px'}}
bordered
dataSource={this.state.list}
renderItem={
(item, index) => <List.Item onClick={this.handleDleleteItem.bind(this, index)}> {item}</List.Item>
}
/>
</div>
)
}
handleInputChange(e) {
const action = getHandleInputChangeAction(e.target.value);
store.dispatch(action);
}
handleStoreChange() {
this.setState(store.getState()); //当我感知到store的数据发生变化的时候,就去调用store.getState方法,从store里重新取一次数据,然后通过this.setState替换掉当前组件的数据
}
handleBtnClick() {
const action = getHandleBtnClick();
store.dispatch(action);
}
handleDleleteItem(index) {
const action = getHandleDleleteItem(index);
store.dispatch(action);
}
}
export default TodoList;
这么写的话我们代码的可维护性更高。
九. Redux 知识点复习补充
1、Redux 设计和使用的三个原则:
- store 是唯一的 ;
- 只有 store 能够改变自己的内容 ;( reducer 只是把内容返回给 store )
- reducer 必须是纯函数 ;(纯函数:给定固定的输入,就一定会有固定的输出,而且不会有任何副作用)