前言
提出一个问题,为什么会出现 redux ? 它解决了什么问题呢?
React 官网赫然写着 A JavaScript library for building user interfaces
, 意思就是专注于构建用户界面的 JavaScript 库。就是用于将 state / props
的数据、JSX
的代码转换成真实的 html 页面。同 Vue.js 一样,React 并不负责全局状态(数据)的管理。React 中数据传递是自上而下,从父组件传递给子组件的过程。
在项目,我们往往会有这样的需求,根据当前登录的用户的信息展示不同的菜单或者数据,这就需要我们在任意组件中能获取到登录用户的信息,要怎么做呢?如果没有这些框架,我们肯定会想办法将这些全局的信息统一存储到一个地方,设计好对应的数据结构存在一个 js 对象中,并且这个对象必须 单例 的,保证每次获取的数据都是同一个。对于修改逻辑,也要遵循既定的规则,保证每次数据修改都是可追溯的。如果没有这些规则的约定,那么状态(数据)的修改变得混沌无序,代码变得乱七八糟,系统潜在的问题增多。
Redux 出现了,用于项目中状态(数据)的管理。并且 Redux 试图让 state 的变化变得可预测。我们接着向下看。
redux
上图展示了 redux 数据流向的过程。React 的组件或者其他组件通过
store.getState()
从 Store
中取数据。修改数据只能通过 store.dispatch(action)
向 Store
提交修改数据的指令, Store
统一处理数据的修改。Reducers
是对应的逻辑处理函数,接收两个参数 (previousState, action)
分别表示之前的状态 state 的值和操作的指令 action,返回一个新的数据 newState。注意Reducers
是一个纯函数,不直接对接收的数据进行修改,返回一个新的数据 newState; 同时,一旦 (previousState, action)
确定的,多次执行的结果是一样的。整个过程就是 redux 的三大原则:
-
单一数据源:
store
中数据是单例,全局唯一的。 -
State 是只读的: 唯一改变 state 的方法就是触发 action,
store.dispatch(action);
直接修改 state 是不起作用的。 -
reducers 是纯函数 保证在
(previousState, action)
确定的前提下,多次执行的结果是一样的;并且不直接修改 state ,通过 Store 触发 state 的修改。
两外,redux 重要的三个函数:
//初始化store
const store = createStore(reducers);
//获取 state
store.getState();
// 触发 action,请求修改数据
store.dispatch(action);
// 订阅数据变化的回调函数
store.subscribe(()=>{
//to do
});
redux 简单使用
不使用 React 或者 Vue,我们单纯的先通过一个例子,对 redux 进行简单使用。通过 webpack 搭建一个小的项目,并 npm install redux -D
加入到项目中。目录结构如下:
redux-demo
├─node_modules
├─build
│ webpack.dev.config.js //开发模式
│ webpack.pro.config.js //打包模式
└─src
│ index.html
│ index.js //入口文件
├─actions
│ actionCreator.js //action creator; action 制造者
├─reducers
│ index.js //reducers 逻辑
└─store
index.js // store
│ package-lock.json
│ package.json
我们在 index.js 入口文件中,初始化获取 store 中的数据,并给按钮绑定事件,通过 store.dispatch(addCounter(1))
向 store
发送 action。
//index.js
import store from './store';
import {addCounter} from './actions/actionCreator';
// 初始化
const stateData = store.getState();
document.getElementById("counter").innerHTML = stateData.counter;
document.getElementById("otherUsed").innerHTML = stateData.counter;
document.getElementById("myBtn").addEventListener("click", ()=>{
// 触发
store.dispatch(addCounter(1));
})
// 订阅
store.subscribe(()=>{
const stateData = store.getState();
document.getElementById("counter").innerHTML = stateData.counter;
document.getElementById("otherUsed").innerHTML = stateData.counter;
})
Reduces 负责根据 action 的 type 对数据逻辑进行处理:
// reducers/index.js
const defaultState = {
counter: 10
}
export default (state = defaultState, action)=>{
if(action.type==="add"){
const newState = JSON.parse(JSON.stringify(state));
newState.counter = newState.counter + action.value;
return newState;
}
return state;
}
这个例子就是为了说明 redux 与 react 不是强耦合的,完全可以独立使用。
源码地址:github.com/YY88Xu/redu…
react + redux 结合(todoList)
利用 create-react-app
初始化项目,目录结构如下:
todo-list
│ package-lock.json
│ package.json
│ README.md
│
├─public
│ │ favicon.ico
│ │ index.html
│ │ logo192.png
│ │ logo512.png
│ │ manifest.json
│ │ robots.txt
│ │
│ └─api
│ initList.json
│
└─src
│ App.css
│ App.js
│ App.test.js
│ index.css
│ index.js
│ logo.svg
│ reportWebVitals.js
│ setupTests.js
│
├─common
│ actionTypes.js
│
├─react-redux // react-redux 实现
│ ├─actions
│ │ actionCreator.js
│ │
│ ├─components
│ │ TodoList.js
│ │
│ ├─reducers
│ │ index.js
│ │
│ └─store
│ index.js
│
├─redux // redux 实现
│ ├─actions
│ │ actionCreator.js
│ │
│ ├─components
│ │ TodoList.js
│ │
│ ├─reducers
│ │ index.js
│ │
│ └─store
│ index.js
│
├─redux-saga // redux-saga 实现
│ ├─actions
│ │ actionCreator.js
│ │
│ ├─components
│ │ TodoList.js
│ │
│ ├─reducers
│ │ index.js
│ │
│ ├─saga
│ │ index.js
│ │
│ └─store
│ index.js
│
└─redux-thunk // redux-thunk 实现
├─actions
│ actionCreator.js
│
├─components
│ TodoList.js
│
├─reducers
│ index.js
│
└─store
index.js
actions
函数形式,返回 action
对象,通常具有type
属性。负责指令的生成,页面通过 store.dispatch(action)
向 store
发送数据修改的请求。
reducers
一个纯函数,接收两个参数 (previousState, action)
第一个表示修改之前的 state 的值,action
是上一步页面通过 store.dispatch(action)
向 store
传递的 action
。
reducers
通过 action
的 type
的值进行不同的处理,返回一个新的变量 newState
。reducers
不能直接修改传递进来的 previousState
.
store
通过 const store = createStore(reducer);
创建一个 store
对象。
需要导入到 React 组件文件中,通过 this.state = store.getState();
让组件获取 store 中的数据;
并在 componentDidMount
生命周期函数中订阅 store 中数据的变化:
componentDidMount(){
store.subscribe(()=>{
this.setState(store.getState());
});
}
react-redux
react-redux 是将 react 和 redux 结合使用,我们需要先安装下依赖:
npm install react-redux -D
<Provider>
组件, 可以简化我们每次导入到 React 组件文件 store 的工作。
import { Provider } from "react-redux";
<Provider store={store}> //store 传进去
<TodoList/>
</Provider>
mapStateToProps
函数,将 store 中的state
转为组件的props
mapDispatchToProps
函数, 传入dispatch
用于触发 action。connect
函数。
const mapStateToProps = (state)=>({
inputValue: state.inputValue,
list: state.list
})
const mapDispatchToProps = (dispatch) => ({
hanldeAdd: ()=>{
dispatch(addItem());
},
changeInputValue: (e)=>{
dispatch(changeValue(e.target.value));
},
deleteItem: (key)=>{
dispatch(deleteItem(key))
}
})
// 接收 mapStateToProps 和 mapDispatchToProps 两个参数,
//并返回一个函数,这个函数接受 TodoList 返回一个封装的组件。
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
异步
两个处理异步的 Redux 的中间件 redux-thunk
和 redux-saga
。
还是提出一个问题? 中间件指的是哪里和哪里的中间呢?
答案是: actions 和 reducers 之间 “ 动一些手脚 ”,先处理异步的请求,再去触发 dispatch
向 store
发起操作。
如果没有这些中间件,我们也可以实现相应的业务需求。使用这些中间件是将异步逻辑剥离出来的一种方式,代码整洁,维护性更高。
redux-thunk
同 redux 一起使用,增强了 redux 的功能。之前 actions
返回一个对象,异步的 action 可以返回一个函数:
// actionCreator.js
export const getInitList = ()=> {
return function(dispatch){
axios.get("/api/initList.json").then(res=>{
dispatch(initList(res.data))
})
}
}
同时,创建 store 的时候也要加上 redux-thunk
这个中间件:
import { createStore, applyMiddleware } from "redux";
import thunk from 'redux-thunk';
import reducer from '../reducers';
const store = createStore(reducer, applyMiddleware(thunk));
export default store;
详细用法见项目 redux-thunk
文件夹下的代码。
redux-saga
redux-saga 也是 redux 的一个中间件,可以处理异步 action 。通过 takeEvery(GET_INIT_LIST, getInitList)
监听 action.type 是GET_INIT_LIST
的action操作,会执行 getInitList
方法,去获取异步数据。
// saga/index.js
import {takeEvery, put} from 'redux-saga/effects';
import { GET_INIT_LIST } from "../../common/actionTypes.js";
import { initList} from "../actions/actionCreator.js";
import axios from 'axios';
function* getInitList(){
try{
const res = yield axios.get("/api/initList.json");
const action = initList(res.data);
yield put(action);
}catch(e){
console.log("initList.json网络请求失败")
}
}
function* mySaga(){
yield takeEvery(GET_INIT_LIST, getInitList);
}
export default mySaga;
详细用法见项目 redux-saga
文件夹下的代码。
源码
最后
如果有错误或者不严谨的地方,烦请给予指正,十分感谢。如果喜欢或者有所启发,欢迎点赞,对作者也是一种鼓励。