Redux初识一

写在前面:
新手学习,不喜轻喷。

1.个人初解

redux将整个应用的state存储树级机构的object中,树级结构的顶点为唯一Store,在应用的整个生命周期内,只会存在一个Store。
store中存放state,要修改store中的state必须通过action来触发,使用reducer里面的函数对state修改,个人觉得有点类似于函数式编程。

2.数据流向

2.1.redux是严格的单向数据流

即:view触发action,reducer接收action修改store中的state,view根据判断,重新渲染。

redux.png

2.2.同一个action得到的新的state数据值一定是相等的

reducer是纯函数,只是用来计算下一个state,函数结果是可以预测的,相同的输入,相同的输出,不应该有副作用。

3.redux在运行过程中到底做了什么事情

3.1. redux/src/index.js redux的入口文件

先看一段redux入口源码

import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'
import __DO_NOT_USE__ActionTypes from './utils/actionTypes' //redux自带的action类型

/*
 * This is a dummy function to check if the function name has been altered by minification.
 * If the function has been minified and NODE_ENV !== 'production', warn the user.
 * 判断在非生产环境下,redux代码是否被压缩,使用function.name来进行判断,无实际意义和方法体
 */
function isCrushed() {}

if (
  process.env.NODE_ENV !== 'production' && //当前环境不是生产环境
  typeof isCrushed.name === 'string' && //方法名称是String类型,IE不支持Function.name
  isCrushed.name !== 'isCrushed' //当代码被编译压缩之后,方法名称会改变,该判断标识当前没有被编译和压缩
) {
  warning(
    '您当前正在使用node_env==“production”之外的缩小代码。 ' +
    '这意味着您正在运行一个较慢的redux开发版本。' +
    '你可以使用loose envify(https://github.com/zertosh/loose-envify)来浏览 ' +
    '或在webpack中设置生产模式(https://webpack.js.org/concepts/mode/)' +
    '以确保生产版本的代码正确。'
  )
}

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes //语义:不要使用ActionTypes
}

该文件主要导出变量和检查当前环境下是否有压缩代码,redux压缩之后,运行性能会有所降低。

关于代码压缩简单理解:
未压缩
function isCrushed() {}
if (
process.env.NODE_ENV !== 'production' &&
typeof isCrushed.name === 'string' &&
isCrushed.name !== 'isCrushed'
)
压缩之后,方法名称会改变,所以等式成立
function d(){}"string"==typeof d.name&&"isCrushed"!==d.name

3.2.redux/src/createStore

createStore(reducer:any,preloadedState?:any,enhancer?:middleware),最终返回一个\color{red}{state tree}实例。可以进行\color{red}{getState}\color{red}{subscribe}监听和 \color{red}{dispatch}派发。

看一段代码:

/**
*创建保存状态树的redux存储。
*更改存储区中数据的唯一方法是对其调用“dispatch()”。
*
*你的应用中应该只有一个store。指定不同的
*部分状态树响应操作,可以组合多个还原器
*通过使用“combinereducers”将其转换为单个reducer函数。
*
*@param{function}reducer返回下一个状态树的函数,给定
*当前状态树和要处理的操作。
*
*@param{any}[preloadedstate]初始状态。您可以选择指定它
*在通用应用程序中对服务器的状态进行优化,或恢复
*以前序列化的用户会话。
*如果使用'combinereducers'生成根还原函数,则必须
*与“combinereducers”键形状相同的对象。
*
*@param{function}[enhancer]存储增强器。您可以选择指定它
*为了增强store的第三方功能,如中间件,
*时间旅行、坚持等。redux附带的唯一存储增强功能
*是“applymiddleware()”。
*
*@return{store}一个redux存储,用于读取状态、分派操作
*并订阅更改。
*/
export default function createStore(reducer, preloadedState, enhancer) {
if (
    (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
    (typeof enhancer === 'function' && typeof arguments[3] === 'function')
  ) {
    throw new Error(
      'It looks like you are passing several store enhancers to ' +
        'createStore(). This is not supported. Instead, compose them ' +
        'together to a single function.'
    )
  }
  
  //检查增强器
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }
  //增强器只能是函数
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
    //返回增强后的store
    return enhancer(createStore)(reducer, preloadedState)
  }
  //reducer也只能是函数
  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }
}

enhancer?:middle,增强器,若干个中间件可以通过applymiddleware产生一个增强器,增强器通过Compose函数合并成一个增强器。
applymiddleware代码如下:

export default function applyMiddleware(...middlewares) {
    // 接受若干个中间件参数
   // 返回一个enhancer增强器函数,enhancer的参数是一个createStore函数。等待被enhancer(createStore)
  return createStore => (...args) => {
    // 先创建store或者更新Store
    const store = createStore(...args)
    // 如果没有创建完成就被调用,抛出异常
    let dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }
    //暂时存储更新前的Store
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    //遍历中间件,将更新前的Store传入,获取更新后的tore数组
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    //组合中间件,将dispatch传入,即每个中间件都有一个增强后的dispatch
    dispatch = compose(...chain)(store.dispatch)
    //返回一个store和dispatch,即:返回实现了中间件的store增强器
    return {
      ...store,
      dispatch
    }
  }
}

调用逻辑:
1.通过createStore方法创建出一个store
2.定一个dispatch,如果在中间件构造过程中调用,抛出错误提示
3.定义middlewareAPI,有两个方法,一个是getState,另一个是dispatch,将其作为中间件调用的store的桥接
4.middlewares调用Array.prototype.map进行改造,存放在chain
5.用compose整合chain数组,并赋值给dispatch
6.将新的dispatch替换原先的store.dispatch

compose代码如下:

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

compose接收>0个的增强器,如果没有增强器,就返回一个空函数,如果有一个函数,返回函数本身,如果是多个,才会产生合并的过程即compose的过程,最后一行,通过迭代器生成组合迭代函数。
看到这里,很懵逼有没有?有没有?


6af89bc8gw1f8rntaeadij20hs0hsgm7.jpg

redux-thunk、redux-saga都是redux常用的中间件,一般配合applyMiddleware使用,applyMiddleware的作用就是将这些enhancer格式化成redux想要的enhancer,以redux-thunk举个栗子

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

根据源码来看,我们如果单独使用thunk,应该如下:

thunk = ({ dispatch, getState })=>{
    return next => action => {
        if (typeof action === 'function') {
            return action(dispatch, getState);
        }
        return next(action);
    };
 } 

applyMiddleware处理之后,应该是china数组如下:

const newDispatch;
const middlewareAPI={
  getState:store.getState,
  dispatch: (...args) => newDispatch(...args)
}
const { dispatch, getState } = middlewareAPI;
const  fun1 = (next)=>{
  return action => {
    if (typeof action === 'function') {
        return action(dispatch, getState);
    }
    return next(action);
  }
}
const chain = [fun1]

模拟整个过程:

function makeASandwichWithSecretSauce(forPerson) {
  return function (dispatch) {
    return fetchSecretSauce().then(
      sauce => dispatch(makeASandwich(forPerson, sauce)),
      error => dispatch(apologize('The Sandwich Shop', forPerson, error))
    );
  };
}
// store.dispatch就等价于newDispatch
store.dispatch(makeASandwichWithSecretSauce('Me'))

====> 转换
const forPerson = 'Me';
const action = (dispatch)=>{
    return fetchSecretSauce().then(
      sauce => dispatch(makeASandwich(forPerson, sauce)),
      error => dispatch(apologize('The Sandwich Shop', forPerson, error))
    );
}
newDispatch()

===> typeof action === 'function' 成立时

 ((dispatch)=>{
    return fetchSecretSauce().then(
      sauce => dispatch(makeASandwich(forPerson, sauce)),
      error => dispatch(apologize('The Sandwich Shop', forPerson, error))
    );
  })( (...args) => newDispatch(...args), getState)

====> 计算运行结果
const forPerson = 'Me';
const dispatch = (...args) => newDispatch(...args) ;
fetchSecretSauce().then(
      sauce => dispatch(makeASandwich(forPerson, sauce)),
      error => dispatch(apologize('The Sandwich Shop', forPerson, error))
);
// 其中:
function fetchSecretSauce() {
  return fetch('https://www.google.com/search?q=secret+sauce');
}
function makeASandwich(forPerson, secretSauce) {
  return {
    type: 'MAKE_SANDWICH',
    forPerson,
    secretSauce
  };
}

function apologize(fromPerson, toPerson, error) {
  return {
    type: 'APOLOGIZE',
    fromPerson,
    toPerson,
    error
  };
}
====> 我们这里只计算Promise.resolve的结果,并且假设fetchSecretSauce返回值为'666',即sauce='666'

const forPerson = 'Me';
const dispatch = (...args) => newDispatch(...args) ;
dispatch({
    type: 'MAKE_SANDWICH',
    'Me',
    '666'
})
====> 为了方便对比,我们再次转换一下

const action = {
    type: 'MAKE_SANDWICH',
    'Me',
    '666'
};

const next = store.dispatch

const newDispatch = action =>{
  if (typeof action === 'function') {
    return action(dispatch, getState);
  }
  return next(action);
}

newDispatch(action)

====> 最终结果
store.dispatch({
    type: 'MAKE_SANDWICH',
    'Me',
    '666'
});
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容