react
和状态管理redux
是紧密结合的,而自己又没有任何联系。react
能够不使用redux
管理状态,redux
也能够脱离react
独立存在。随着react
的项目愈来愈复杂,state
变的繁重,各类prop
和state
的转变让咱们在开发过程当中变得头晕眼花,react
原本就是一个专一于UI层的库,本不该该让繁杂的prop
和state
的逻辑掺杂进来。因而Flux
的架构出现了,Flux
架构模式用于抽离react
的state
能更好的去构建项目,Flux
架构模式的实践有好多中,显然redux
是成功的。前端
我在接触react
和redux
以前老是听好多人提起redux
这个东西,我心想它到底有什么魔力,让那么多的人为之惊叹,今天就来揭开redux
的真面目。react
前面提到redux
是能够脱离react
存在的,这句话的意思是redux
并非依附于react
的,即使是用jQuery
+redux
也是能够的。redux
提供的是一种状态管理的方式,同时也定义了一种管理状态的规则,全部须要使用这个小而美的库的项目都必须遵循这个规则,也正是这个规则使用redux再书写过程当中有了可预测性和可追溯性。面试
谈redux
必然要谈谈它的设计原则,就如同想要更明白的了解同样东西,就须要先了解它是怎么来的,固然历史明白上面这些就够了。编程
redux
有三大设计原则redux
单一数据源的意思是说整个react
项目的state
都存放在一块儿,也能够认为存在一个大对象中,单一数据源可让咱们在项目中更专一于数据源的设计与构建上。数组
使用过redux
都知道,视图是经过store.getState()
方法来获取状态的,经过dispatch
派发action
来改变状态。状态是只读的也就是说咱们只能经过stiore.getState()
来获取状态,只能经过dispatch
派发action
来改变状态。这也体现了单一数据流动,让咱们在构建项目的时候只须要关于一个方向的数据流动。架构
我当时在学的时候也是有这样的疑问:为何要使纯函数来写,什么是纯函数?并发
所谓纯函数:对于一个函数来讲相同的输入一定有相同的输出, 即不依赖外部环境,也不改变外部环境,这样的函数就叫作纯函数。纯函数纯的,是没有反作用的。app
明白了纯函数,那么在写reducer
的时候必定见过这么一段代码。异步
const state = reducer(initstate = {},action);
复制代码
上面代码,再结合纯函数,就能够说对于特定的action
和initstate
一定会获得相同的state
,这里正是体现了redux
的可预测性。
redux
提供了一系列规则来规定咱们来写代码。能够大体分为四个角色:
action
是承载状态的载体,通常action
将视图所产出的数据,发送到reducer进行处理。action
的书写格式通常是这样:
const addAction = {
type:"ADD",
value:.....
}
复制代码
action
其实就是一个JavaScript对象,它必需要有一个type属性用来标识这个action
是干吗的(也能够认为家的地址,去reducer中找家),value属性是action携带来自视图的数据。
action
的表示方式也能够是一个函数,这样能够更方面的构建action
,但这个函数必须返回一个对象。
const addAction = (val) => ({
type:"ADD",
value: val
})
复制代码
这样拿到的数据就灵活多了。
对于action
的type属性,通常若是action变的庞大的话会把全部的type抽离出来到一个constants中,例如:
const ADDTODO = 'ADDTODO',
const DELETETODO = 'DELETEDOTO'
export {
ADDTODO,
DELETETODO,
}
复制代码
这样可让type更清晰一些。
reducer
指定了应用状态的变化如何响应 actions
并发送到 store
。 在redux
的设计原则中提到使用纯函数来编写reducer
,目的是为了让state变的可预测。reducer
的书写方式通常是这样:
const reducer = (state ={},action){
switch(action.type){
case :
......
case :
......
case :
......
default :
return state;
}
}
复制代码
使用switch判断出什么样的action
应该使用什么样的逻辑去处理。
当随着业务的增多,那么reducer
也随着增大,显然一个reducer
是不可能的,因而必需要拆分reducer
,拆分reducer
也是有必定的套路的:好比拆分一个TodoList,就能够把todos操做放在一块儿,把对todo无关的放在一块儿,最终造成一个根reducer。
function visibilityFilter(state,action){
switch(action.type){
case :
......
case :
......
default :
return state;
}
}
function todos(state,action){
switch(action.type){
case :
......
case :
......
default :
return state;
}
}
//根reducer
function rootReducer(state = {}, action) {
return {
visibilityFilter: visibilityFilter(state.visibilityFilter, action),
todos: todos(state.todos, action)
}
}
复制代码
这样作的好处在于业务逻辑的分离,让根reducer
再也不那么繁重。好在redux
提供了combineReducers
方法用于构建rootReducer
const rootReducer = combineReducers({
visibilityFilter,
todos,
})
复制代码
这部分代码和上面rootReducer的做用彻底相同。它的原理是经过传入对象的key-value把全部的state进行一个糅合。
dispatch
的做用是派发一个action
去执行reducer
。我以为dispatch
就是一个发布者,和subscribe
一块儿组合成订阅发布者模式。使dispatch
派发:
const action = {
type: "ADD",
value: "Hello Redux",
}
dispatch(action);
复制代码
store
能够说是redux
的核心了。开头也提到store
是redux
状态管理的惟一数据源,除此以外,store
仍是将dispatch
、reducer
等联系起来的命脉。
store
经过redux
提供的createStore
建立,它是一个对象,有以下属性:
建立store:
const store = Redux.createStore(reducer,initialState,enhancer);
//1. reducer就是咱们书写的reducer
//2. initialState是初始化状态
//3. enhancer是中间件
复制代码
在建立store
的时候createStore
是能够传入三个参数的,第三个参数就是中间件,使用redux
提供的applyMiddleware
来调用,applyMiddleware
至关因而对dispatch
的一种加强,经过中间件能够在dispatch
过程当中作一些事情,好比打logger、thunk(异步action)等等。
使用方式以下:
//异步action中间件
import thunk from "redux-thunk";
const store = Redux.createStore(reducer,initialState,applMiddleware(thunk));
复制代码
思想先告一段落,既然懂得了redux
的思想,那么接下来手下一个简易版的redux
。
新的react-hooks中除了useReducer
,集成了redux
的功能,为何还要深刻了解redux
呢?
随着前端技术的迭代,技术的快速更新,咱们目前并无能力去预知或者去引领前端的发展,惟一能作的就是在时代中吸取知识并消化知识,虽然将来有可能redux
会被useReducer
所取代,可是思想是不变的,redux
这个小而美的库设计是奇妙的,也许有哪一天在写业务的时候遇到了某种类似的需求,咱们也能够经过借助于这个库的思想去作一些事情。
要想了解redux
,必然要先了解它的核心,它的核心就是createStore
这个函数,store
、getState
,dispatch
都在这里产出。我我的以为createStore
是一个提供一系列方法的订阅发布者模式:经过subscribe
订阅store
的变化,经过dispatch
派发。那么下面就来实现一下这个createStore
。
从上面store
中能够看出。建立一个store
须要三个参数;
//1.接受的rootReducer
//2.初始化的状态
//3.dispatch的加强器(中间件)
const createStore = (reducer,initialState,enhancer) => {
};
复制代码
createStore
还返回一些列函数接口提供调用
const crateStore = (reducer, initialState, enhancer) => {
return {
getState,
dispatch,
subscribe,
replaceReducer,
}
}
复制代码
如下代码都是在createStore内部
getStore
方法的做用就是返回当前的store
。
let state = initialState;
const getState = () => {
return state;
}
复制代码
subscribe
是createStore
的订阅者,开发者经过这个方法订阅,当store
改变的时候执行监听函数。subscribe
是典型的高阶函数,它的返回值是一个函数,执行该函数移除当前监听函数。
//建立一个监听时间队列
let subQueue = [];
const subscribe = (listener) => {
//把监听函数放入到监听队列里面
subQueue.push(listener);
return () => {
//找到当前监听函数的索引
let idx = subQueue.indexOf(listener);
if(idx > -1){
//经过监听函数的索引把监听函数移除掉。
subQueue.splice(idx,1);
}
}
}
复制代码
dispatch
是createStore
的发布者,dispatch
接受一个action
,来执行reducer
。dispatch
在执行reducer
的同时会执行全部的监听函数(也就是发布)。
let currentReducer = reducer;
let isDispatch = false;
const dispatch = (action) => {
//这里使用isDispatch作标示,就是说只有当上一个派发完成以后才能派发下一个
if(isDispatch){
throw new Error("dispatch error");
}
try{
state = currentReducer(state,action);
isDispatch = true;
}finally{
isDispatch = false;
}
//执行全部的监听函数
subQueue.forEach(sub => sub.apply(null));
return action;
}
复制代码
replaceReducer
顾名思义就是替换reducer
的意思。再执行createState
方法的时候reducer
就做为第一个参数传进去,若是后面想要从新换一个reducer
,来代码写一下。
const replaceReducer = (reducer) => {
//传入一个reduce做为参数,把它赋予currentReducer就能够了。
currentReducer = reducer;
//更该以后会派发一次dispatch,为何会派发等下再说。
dispatch({type:"REPLACE"});
}
复制代码
上面已经实现了createStore
的四个方法,剩下的就是replaceReducer
中莫名的派发了一个type
为REPLACE
的action
,并且翻到源码的最后,也派发一个type
为INIT
的action
,为何呢?
其实当使用createStore
建立Store
的时候,咱们都知道,第一个参数为reducer
,第二个参数为初始化的state
。当若是不写第二个参数的时候,咱们再来看一下reducer
的写法
const reducer = (state = {}, action){
switch(action.type){
default:
return state;
}
}
复制代码
通常在写reducer
的时候都会给state
写一个默认值,而且default
出默认的state
。当createStore
不存在,这个默认值如何存储在Store
中呢?就是这个最后派发的type:INIT
的做用。在replaceReducer
中派发也是这个缘由,更换reducer
后派发。
如今已经实现的差很少了,只要再加一些容错就能够了。
/** * * @param {*} reducer //reducer * @param {*} initState //初始状态 * @param {*} middleware //中间件 */
const createStore = (reducer, initState,enhancer) => {
let initialState; //用于保存状态
let currentReducer = reducer; //reducer
let listenerQueue = []; //存放全部的监听函数
let isDispatch = false;
if(initState){
initialState = initState;
}
if(enhancer){
return enhancer(createStore)(reducer,initState);
}
/** * 获取Store */
const getState = () => {
//判断是否正在派发
if(isDispatch){
throw new Error('dispatching...')
}
return initialState;
}
/** * 派发action 并触发全部的listeners * @param {*} action */
const dispatch = (action) => {
//判断是否正在派发
if(isDispatch){
throw new Error('dispatching...')
}
try{
isDispatch = true;
initialState = currentReducer(initialState,action);
}finally{
isDispatch = false;
}
//执行全部的监听函数
for(let listener of listenerQueue){
listener.apply(null);
}
}
/** * 订阅监听 * @param {*} listener */
const subscribe = (listener) => {
listenerQueue.push(listener);
//移除监听
return function unscribe(){
let index = listenerQueue.indexOf(listener);
let unListener = listenerQueue.splice(index,1);
return unListener;
}
}
/** * 替换reducer * @param {*} reducer */
const replaceReducer = (reducer) => {
if(reducer){
currentReducer = reducer;
}
dispatch({type:'REPLACE'});
}
dispatch({type:'INIT'});
return {
getState,
dispatch,
subscribe,
replaceReducer
}
}
export default createStore;
复制代码
在redux
中提供了一个组合函数,若是你知道函数式编程的话,那么对compose
必定不陌生。若是不了解的话,那我说一个场景确定就懂了。
//有fn1,fn2,fn3这三个函数,写出一个compose函数实现一下功能
//1. compose(fn1,fn2,fn3) 从右到左执行。
//2. 上一个执行函数的结果做为下一个执行函数的参数。
const compose = (...) => {
}
复制代码
上面的需求就是compose
函数,也是一个常考的面试题。如何实现实现一个compose
?一步一步来。
首先compose
接受的是一系列函数。
const compose = (...fns) => {
}
复制代码
从右到左执行,咱们采用数组的reduce
方法,利用惰性求值的方式。
const compose = (...fns) => fns.reduce((f,g) => (...args) => f(g(args)));
复制代码
这就是一个compose
函数。
redux
中的中间件就是对dispatch
的一种加强,在createStore
中实现这个东西很简单。源码以下:
const createStore = (reducer,state,enhancer) => {
//判断第三个参数的存在。
if(enhancer && type enhancer === 'function') {
//知足enhance存在的条件,直接return,组织后面的运行。
//经过柯里化的方式传参
//为何传入createStore?
//虽然是加强,天然返回以后依然是一个store对象,因此要使用createStore作一些事情。
//后面两个参数
//中间件是加强,必要的reducer和state也必要经过createStore传进去。
return enhancer(crateStore)(reducer,state);
}
}
复制代码
上面就是中间件再createStore
中的实现。
中间件的构建经过applyMiddleware
实现,来看一下applyMiddleware
是怎么实现。由上面能够看出applyMiddleware
是一个柯里化函数。
const applyMiddleware = (crateStore) => (...args) => {
}
复制代码
在applyMiddleware
中须要执行createStore
来获得接口方法。
const applyMiddleware =(...middlewares) => (createStore) => (...args) => {
let store = createStore(...args);
//占位dispatch,避免在中间件过程当中调用
let dispatch = () => {
throw new Error('error')
}
let midllewareAPI = {
getState: store.getState,
dispatch,
}
//把middlewareAPI传入每个中间件中
const chain = middlewares.map(middleware => middleware(middlewareAPI));
//加强dispatch生成,重写占位dispatch,把store的默认dispatch传进去,
dispatch = compose(...chain)(store.dispatch);
//最后把加强的dispatch和store返回出去。
return {
...store,
dispatch
}
}
复制代码
上面就是applyMiddleware
的实现方法。
根据applyMiddleware
中间件参数的传入,能够想出一个基本的中间件是这样的:
const middleware = (store) => next => action => {
//业务逻辑
//store是传入的middlewareAPI
//next是store基础的dispatch
//action是dispatch的action
}
复制代码
这就是一个中间件的逻辑了。
在写逻辑的时候必然会用到异步数据的,咱们知道reducer
是纯函数,不容许有反作用操做的,从上面到如今也能够明白整个redux
都是函数式编程的思想,是不存在反作用的,那么异步数据怎么实现呢?必然是经过applyMiddleware
提供的中间件接口实现了。
异步中间件必需要求action
是一个函数,根据上面中间件的逻辑,咱们来写一下。
const middleware = (store) => next => action => {
if(typeof action === 'function'){
action(store.dispatch,store.getState);
}
next(action);
}
复制代码
判断传入的action
是不是一个函数,若是是函数使用加强dispatch
,若是不是函数使用普通的dispatch
。
到此为止就是我能力范围内所理解的Redux
。我我的认为,要学习一个东西必定要看一下它的源码,学习它的思想。技术更新迭代,思想是不变的,无非就是思想的转变。若是有不对的地方,还望大佬们指点。