一、redux介绍
2014年 Facebook 提出了 Flux 架构的概念,引发了很多的实现。但是使用中发现很多不完美的地方,2015年,Redux 出现将 Flux 与函数式编程结合一起,关系如下:
1、引入redux
yarn add redux
2、store
即store/index.js,该导出的store对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。当前时刻的 State,可以通过store.getState()拿到。通常如下结构:
import {createStore} from "redux";
import reducer from "./reducer";
/**
* 第二个参数是调试google浏览器的调试插件redux-devtools,可以不要
* @type {Store<unknown, Action>}
*/
const store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
export default store
3、reducer
reducer专门用户数据的处理,他不能直接修改state的数据,需要通过拷贝state改变数据,然后return返回该对象。
const newStatus = JSON.parse(JSON.stringify(state));
即如上只能修改newStatus的数据。通常结构如下:
const defaultStatus = {
inputValue: "",
list: [1, 2, 3]
}
//reducer可以接收status,但是不可以修改status,这就是拿到status为什么不修改,而是直接拷贝
export default (status = defaultStatus, action) => {
//在这里修改数据
const newStatus = JSON.parse(JSON.stringify(status));
return newStatus;
}
defaultStatus是源数据对象,也是默认的数据。
4、action
在上面中可以看到有个参数action,要发送多少种消息,就会有多少种 Action,一个action表示一种行为数据,在view变化的数据都会通过store.dispatch(action)发送到这块,统一在这里处理。如果都手写,会很麻烦。可以可以定义一个函数来生成 Action,这个函数就叫 actionCreators,后面会介绍。
5、store.dispatch
store.dispatch用来发送view变化的数据,例如,监听input输入框输入的源数据,将数据发送到action处理
onChange={(e) => {
const action = {
inputValue: e.target.value,
type: "input_value"
}
store.dispatch(action)
}}
6、store.subscribe
注册数据监听函数,一旦 State 发生变化,就自动执行这个函数,这时候可以在App的该函数中设置数据。
constructor(prop) {
super(prop);
this.state = store.getState();
//订阅了store.subscribe,只要store发生改变,handleStoreChange就会被调用
store.subscribe(this.handleStoreChange);
}
handleStoreChange() {
this.setState(store.getState());
}
7、redux三大原则:
1、store必须是唯一的。
2、只有store可以改变自己的内容。
3、reducer必须是纯函数。即:给定输入,必定有输出,没有其他副作用。
关系如下:
二、redux实现todolist
1、引入antd和redux
$ yarn add antd
$ yarn add redux
2、创建store
store包含
actionTypes.js:action列表,类似于Java中的枚举类。
actionCreators.js:创建action,类似于Java中的Factory模式。
index.js:中间人,用于传递数据,是view和reducer的桥梁。
reducer.js:实际的数据处理类。接收数据——>处理数据——>返回数据。
(1)actionTypes
//输入框改变数据的type
export const CHANGE_INPUT_VALUE= 'change_input_value';
//添加数据的type
export const ADD_TODO_ITEM= 'add_todo_item';
//删除数据的type
export const DELETE_TODO_ITEM= 'delete_todo_item';
(2)actionCreators
import {ADD_TODO_ITEM, CHANGE_INPUT_VALUE, DELETE_TODO_ITEM} from "./actionTypes";
//获取数据框改变的action
export const getInputChangeAction = (value) => ({
type: CHANGE_INPUT_VALUE,
value
});
//获取添加数据action
export const getAddTodoItem=()=> ({
type: ADD_TODO_ITEM
})
//删除数据的action
export const getDeleteTodoItem = (index) => ({
type: DELETE_TODO_ITEM,
value: index
})
(3)index
import {createStore} from "redux";
import reducer from "./reducer";
/**
* 第二个参数是调试google浏览器的调试插件redux-devtools,可以不要
* @type {Store<unknown, Action>}
*/
const store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
export default store
(3)reducer
import {CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM} from './actionTypes'
const defaultStatus = {
inputValue: "",
list: [1, 2, 3]
}
//reducer可以接收status,但是不可以修改status,这就是拿到status为什么不修改,而是直接拷贝
export default (status = defaultStatus, action) => {
if (action.type === CHANGE_INPUT_VALUE) {
//数据的深拷贝
const newStatus = JSON.parse(JSON.stringify(status));
//修改拷贝的数据
newStatus.inputValue = action.value;
//将修改后的数据返回,返回给了store
return newStatus;
} else if (action.type === ADD_TODO_ITEM) {
const newStatus = JSON.parse(JSON.stringify(status));
newStatus.list.push(newStatus.inputValue);
newStatus.inputValue = '';
return newStatus;
} else if ((action.type === DELETE_TODO_ITEM)) {
const newStatus = JSON.parse(JSON.stringify(status));
newStatus.list.splice(action.value, 1);
return newStatus;
}
return status;
}
3、创建TodoList,订阅store.subscribe
import React, {Component} from 'react'
import 'antd/dist/antd.css';
import store from "./store";
import {getAddTodoItem, getDeleteTodoItem, getInputChangeAction} from "./store/actionCreators";
import TodoListUI from "./TodoListUI";
/**
* redux三大原则:
* 1、store必须是唯一的。
* 2、只有store可以改变自己的内容。
* 3、reducer必须是纯函数。即:给定输入,必定有输出,没有其他副作用。
*/
class TodoList extends Component {
constructor(prop) {
super(prop);
this.state = store.getState();
this.handleInputChange = this.handleInputChange.bind(this);
this.handleStoreChange = this.handleStoreChange.bind(this);
this.handleClick = this.handleClick.bind(this);
this.handleItemClick = this.handleItemClick.bind(this)
//订阅了store.subscribe,只要store发生改变,handleStoreChange就会被调用
store.subscribe(this.handleStoreChange);
}
render() {
return (
<TodoListUI
inputValue={this.state.inputValue}
list={this.state.list}
handleInputChange={this.handleInputChange}
handleClick={this.handleClick}
handleItemClick={this.handleItemClick}
/>
)
}
/**
* 监听输入框内容
* @param e
*/
handleInputChange(e) {
const action = getInputChangeAction(e.target.value);
store.dispatch(action);
}
handleStoreChange() {
this.setState(store.getState());
}
handleClick() {
if (this.state.inputValue === '') return;
const action = getAddTodoItem()
store.dispatch(action);
}
handleItemClick(index) {
let action = getDeleteTodoItem(index)
store.dispatch(action);
}
}
export default TodoList;
4、创建TodoListUI
import React, {Component} from "react";
import {Button, Input, List} from "antd";
class TodoListUI extends Component {
render() {
return (
<div style={{marginTop: 10, marginLeft: 20}}>
<Input placeholder="todolist" style={{width: 300, marginRight: 10}} value={this.props.inputValue}
onChange={this.props.handleInputChange}/>
<Button type="primary" onClick={(this.props.handleClick)}>提交</Button>
<List
style={{marginTop: 10, width: 300, marginRight: 10}}
bordered
dataSource={this.props.list}
renderItem={(item, index) => (
//注意这块handleItemClick有一个参数index,需要用到箭头函数,没有参数不需要箭头函数
<List.Item onClick={() => {
this.props.handleItemClick(index)
}}>
{item}
</List.Item>
)}
/>
</div>
);
}
}
export default TodoListUI;
三、redux的中间件
redux常用的中间件有两个redux-thunk和redux-saga,原理如下:
1、redux-thunk
(1)作用
redux-thunk是用于处理action和store之间的中间件,dispatch只能接受一个对象,通过redux-thunk中间件使其可以接受一个函数,以上面的todolist为例,在生命周期函数componentDidMount中请求网络初始化数据:
componentDidMount() {
axios.get('/inputlist.json').then(value => {
const data = value.data;
const action = {
type: INIT_INPUT_VALUE,
data
}
dispatch(action)
});
}
通过redux-thunk可以把网络请求转移到actionCreators.js里面处理。
(2)引入
yarn add redux-thunk
(3)使用
在store/index.js中添加,添加多个中间件的方法参考:https://github.com/zalmoxisus/redux-devtools-extension
import {createStore, applyMiddleware, compose} from "redux";
import reducer from "./reducer";
import thunk from 'redux-thunk';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(thunk),
);
const store = createStore(reducer, enhancer);
export default store;
在actionTypes.js中定一个type:
//初始化
export const TODO_INIT_ITEM = 'todo_init_item';
在actionCreators.js中网络请求
export const getInitItem = (data) => ({
type: TODO_INIT_ITEM,
data
})
export const getTodoList = () => {
return (dispatch) => {
//网络请求
axios.get('/inputlist.json').then(value => {
let action = getInitItem(value.data);
dispatch(action)
});
}
}
在TodoList.js生命周期函数componentDidMount中:
componentDidMount() {
//只有使用了thunker时候这块才能为一个函数
let action = getTodoList();
store.dispatch(action);
}
在reducer中出处理数据:
case TODO_INIT_ITEM:
newState.list = action.data;
break;
2、redux-saga
redux-saga在单独的文件中进行网络请求数据派发给store,不同于redux-thunke在actionCreators.js网络请求。
(1)引入
yarn add redux-saga
(2)使用
在store/index.js中添加,参考:GitHub - redux-saga/redux-saga: An alternative side effect model for Redux apps
import {createStore, compose, applyMiddleware} from "redux";
import reducer from "./reducer";
import createSagaMiddleware from 'redux-saga'
import saga from './todoDoSagas'
/**
* 在单独的文件中进行网络请求数据派发给store
*
* https://github.com/redux-saga/redux-saga
*/
const sagaMiddleware = createSagaMiddleware()
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(
//使用中间件
applyMiddleware(sagaMiddleware),
);
const store = createStore(reducer, enhancer);
sagaMiddleware.run(saga);
export default store;
在actionTypes.js中定一个type:
//初始化
export const TODO_INIT_ITEM = 'todo_init_item';
//请求
export const TODO_REQUEST_ITEM = 'todo_request_item';
创建todoDoSagas.js的文件夹,进行处理:
import {put, takeLatest,takeEvery} from 'redux-saga/effects'
import {TODO_INIT_ITEM, TODO_REQUEST_ITEM} from "./createTypes";
import axios from "axios";
function* todoSagas() {
yield takeEvery(TODO_INIT_ITEM, initItem);
}
function* initItem() {
try {
const item = yield axios.get('/inputlist.json');
//注意这个type类型和takeEvery是不一样的,写成一样的会导致请求进入死循环
const action = {
type: TODO_REQUEST_ITEM,
data: item.data
};
yield put(action)
console.log('请求成功')
} catch (e) {
console.log('请求失败');
}
}
export default todoSagas;
在actionCreators.js中网络请求,注意这块的type类型
export const getTodoList = () => ({
type:TODO_INIT_ITEM
})
在TodoList.js生命周期函数componentDidMount中:
componentDidMount() {
let action = getTodoList();
store.dispatch(action)
}
在reducer中出处理数据:
case TODO_REQUEST_ITEM:
//这块的类型和todoSagas的action一样,和App.js不一样
newState.list = action.data;
break;
目录如下:
效果如下:
最后附上todoList项目四次改造的四个源码:todolist.zip-Web开发文档类资源-CSDN下载