资讯专栏INFORMATION COLUMN

【React进阶系列】手写redux api

Elle / 796人阅读

摘要:每个接受的和函数作为命名参数,并返回一个函数。调用链中最后一个会接受真实的的方法作为参数,并借此结束调用链。

简介: 手写实现redux基础api

createStore( )和store相关方法

api回顾:

createStore(reducer, [preloadedState], enhancer)

</>复制代码

  1. 创建一个 Redux store 来以存放应用中所有的 state
  2. reducer (Function): 接收两个参数,当前的 state 树/要处理的 action,返回新的 state
  3. preloadedState: 初始时的 state
  4. enhancer (Function): store creator 的高阶函数,返回一个新的强化过的 store creator

Store 方法

</>复制代码

  1. getState() 返回应用当前的 state
  2. dispatch(action) 分发 action。这是触发 state 变化的惟一途径
  3. subscribe(listener) 添加一个变化监听器。每当 dispatch action 的时候就会执行,state 树中的一部分可能已经变化
  4. replaceReducer(nextReducer) 替换 store 当前用来计算 state 的 reducer(高级不常用,不作实现)实现 Redux 热加载机制会用到

源码实现:

</>复制代码

  1. ./self-redux.js
  2. export function createStore(reducer, enhancer) {
  3. if(enhancer) {
  4. return enhancer(createStore)(reducer)
  5. }
  6. let currentState = {}
  7. let currentListeners = []
  8. function getState() {
  9. return currentState
  10. }
  11. function subscribe(listeners) {
  12. currentListeners.push(listener)
  13. }
  14. function dispatch(action) {
  15. currentState = reducer(currentState, action)
  16. currentListeners.forEach(v => v())
  17. return action
  18. }
  19. dispatch({ type: "@rainie/init-store" })
  20. return {
  21. getState,
  22. subscribe,
  23. dispatch
  24. }
  25. }

demo:验证正确性

</>复制代码

  1. // import { createStore } from "redux"
  2. // 将redux文件替换成自己实现的redux文件
  3. import { createStore } from "./self-redux.js"
  4. // 这就是reducer处理函数,参数是状态和新的action
  5. function counter(state=0, action) {
  6. // let state = state||0
  7. switch (action.type) {
  8. case "加机关枪":
  9. return state + 1
  10. case "减机关枪":
  11. return state - 1
  12. default:
  13. return 10
  14. }
  15. }
  16. // 新建store
  17. const store = createStore(counter)
  18. const init = store.getState()
  19. console.log(`一开始有机枪${init}把`)
  20. function listener(){
  21. const current = store.getState()
  22. console.log(`现在有机枪${current}把`)
  23. }
  24. // 订阅,每次state修改,都会执行listener
  25. store.subscribe(listener)
  26. // 提交状态变更的申请
  27. store.dispatch({ type: "加机关枪" })
combineReducers(reducers)

api简介

</>复制代码

  1. 把一个由多个不同 reducer 函数作为 valueobject,合并成一个最终的 reducer 函数
  2. 实现 Redux 热加载机制会用到

</>复制代码

  1. import { combineReducers } from "redux"
  2. import todos from "./todos"
  3. import counter from "./counter"
  4. export default combineReducers({
  5. todos,
  6. counter
  7. })

实现:

实质就是返回一个大的function 接受state,action,然后根据key用不同的reducer
注:combinedReducer的key跟state的key一样

</>复制代码

  1. const reducer = combineReducers({
  2. a: doSomethingWithA,
  3. b: processB,
  4. c: c
  5. })
  6. function reducer(state = {}, action) {
  7. return {
  8. a: doSomethingWithA(state.a, action),
  9. b: processB(state.b, action),
  10. c: c(state.c, action)
  11. }
  12. }

</>复制代码

  1. function combindReducer(reducers) {
  2. // 第一个只是先过滤一遍 把非function的reducer过滤掉
  3. const reducerKeys = Object.keys(reducers)
  4. const finalReducers = {}
  5. reducerKeys.forEach((key) => {
  6. if(typeof reducers[key] === "function") {
  7. finalReducers[key] = reducers[key]
  8. }
  9. })
  10. const finalReducersKeys = Object.keys(finalReducers)
  11. // 第二步比较重要 就是将所有reducer合在一起
  12. // 根据key调用每个reducer,将他们的值合并在一起
  13. let hasChange = false;
  14. const nextState = {};
  15. return function combind(state={}, action) {
  16. finalReducersKeys.forEach((key) => {
  17. const previousValue = state[key];
  18. const nextValue = reducers[key](previousValue, action);
  19. nextState[key] = nextValue;
  20. hasChange = hasChange || previousValue !== nextValue
  21. })
  22. return hasChange ? nextState : state;
  23. }
  24. }

applyMiddleware(...middleware)

</>复制代码

  1. 使用包含自定义功能的 middleware 来扩展 Redux 是
  2. ...middleware (arguments): 遵循 Redux middleware API 的函数。
  3. 每个 middleware 接受 Store 的 dispatch 和 getState 函数作为命名参数,并返回一个函数。
  4. 该函数会被传入 被称为 next 的下一个 middleware 的 dispatch 方法,并返回一个接收 action 的新函数,这个函数可以直接调用 next(action),或者在其他需要的时刻调用,甚至根本不去调用它。
  5. 调用链中最后一个 middleware 会接受真实的 store 的 dispatch 方法作为 next 参数,并借此结束调用链。
  6. 所以,middleware 的函数签名是 ({ getState, dispatch }) => next => action

</>复制代码

  1. import { createStore, combineReducers, applyMiddleware } from "redux"
  2. import thunk from "redux-thunk"
  3. import * as reducers from "./reducers"
  4. let reducer = combineReducers(reducers)
  5. // applyMiddleware 为 createStore 注入了 middleware:
  6. let store = createStore(reducer, applyMiddleware(thunk))
中间件机制applyMiddleware的实现

中间件机制图

实现步骤
1.扩展createStore,使其接受第二个参数(中间件其实就是对createStore方法的一次扩展)
2.实现applyMiddleware,对store的disptach进行处理
3.实现一个中间件

正常调用

</>复制代码

  1. import React from "react"
  2. import ReactDOM from "react-dom"
  3. // import { createStore, applyMiddleware} from "redux"
  4. import { createStore, applyMiddleware} from "./self-redux"
  5. // import thunk from "redux-thunk"
  6. import thunk from "./self-redux-thunk"
  7. import { counter } from "./index.redux"
  8. import { Provider } from "./self-react-redux";
  9. import App from "./App"
  10. const store = createStore(counter, applyMiddleware(thunk))
  11. ReactDOM.render(
  12. (
  13. ),
  14. document.getElementById("root"))

</>复制代码

  1. // 便于理解:函数柯利化例子
  2. function add(x) {
  3. return function(y) {
  4. return x+y
  5. }
  6. }
  7. add(1)(2) //3

applymiddleware

</>复制代码

  1. // ehancer(createStore)(reducer)
  2. // createStore(counter, applyMiddleware(thunk))
  3. // applyMiddleware(thunk)(createStore)(reducer)
  4. // 写法函数柯利化
  5. export function applyMiddleware(middleware) {
  6. return function (createStore) {
  7. return function(...args) {
  8. // ...
  9. }
  10. }
  11. }
  12. // 只处理一个 middleware 时
  13. export function applyMiddleware(middleware) {
  14. return createStore => (...args) => {
  15. const store = createStore(...args)
  16. let dispatch = store.dispatch
  17. const midApi = {
  18. getState: store.getState,
  19. dispatch: (...args) => dispatch(...args)
  20. }
  21. // 经过中间件处理,返回新的dispatch覆盖旧的
  22. dispatch = middleware(midApi)(store.dispatch)
  23. // 正常中间件调用:middleware(midApi)(store.dispatch)(action)
  24. return {
  25. ...store,
  26. dispatch
  27. }
  28. }
  29. }
  30. // 处理多个middleware时
  31. // 多个 compose
  32. export function applyMiddleware(...middlewares) {
  33. return createStore => (...args) => {
  34. const store = createStore(...args)
  35. let dispatch = store.dispatch
  36. const midApi = {
  37. getState: store.getState,
  38. dispatch: (...args) => dispatch(...args)
  39. }
  40. const middlewareChain = middlewares.map(middleware => middleware(midApi))
  41. dispatch => compose(...middlewareChain(store.dispatch))
  42. // dispatch = middleware(midApi)(store.dispatch)
  43. // middleware(midApi)(store.dispatch)(action)
  44. return {
  45. ...store,
  46. dispatch
  47. }
  48. }
  49. }

手写redux-thunk异步中间件实现

</>复制代码

  1. // middleware(midApi)(store.dispatch)(action)
  2. const thunk = ({ dispatch, getState }) => next => action => {
  3. // next就是store.dispatch函数
  4. // 如果是函数,执行以下,参数dispatch和getState
  5. if (typeof action == "function") {
  6. return action(dispatch, getState)
  7. }
  8. // 默认 什么都不干
  9. return next(action)
  10. }
  11. export default thunk
  12. 处理异步action
  13. export function addGunAsync() {
  14. // thunk插件作用,这里可以返回函数
  15. return dispatch => {
  16. setTimeout(() => {
  17. // 异步结束后,手动执行dispatch
  18. dispatch(addGun())
  19. }, 2000)
  20. }
  21. }

趁热打铁,再实现一个中间件: dispatch接受一个数组,一次处理多个action

</>复制代码

  1. export arrayThunk = ({ dispatch, getState }) => next => action => {
  2. if(Array.isArray(action)) {
  3. return action.forEach(v => dispatch(v))
  4. }
  5. return next(action)
  6. }
  7. 这类action会被处理
  8. export function addTimes() {
  9. return [{ type: ADD_GUN },{ type: ADD_GUN },{ type: ADD_GUN }]
  10. }
bindActionCreators的实现

在react-redux connect mapDispatchToProps中使用到了该方法,可以去看那篇blog,有详解~

api: bindActionCreators(actionCreators, dispatch)

</>复制代码

  1. 把 action creators 转成拥有同名 keys 的对象,但使用 dispatch 把每个 action creator 包围起来,这样可以直接调用它们

实现:

</>复制代码

  1. function bindActionCreator(creator, dispatch) {
  2. return (...args) => dispatch(creator(...args))
  3. }
  4. export function bindActionCreators(creators, dispatch) {
  5. let bound = {}
  6. Object.keys(creators).forEach( v => {
  7. let creator = creators[v]
  8. bound[v] = bindActionCreator(creator, dispatch)
  9. })
  10. return bound
  11. }
  12. // 简写
  13. export function bindActionCreators(creators, dispatch) {
  14. return Object.keys(creators).reduce((ret, item) => {
  15. ret[item] = bindActionCreator(creators[item], dispatch)
  16. return ret
  17. }, {})
  18. }
compose的实现

api: compose(...functions)

</>复制代码

  1. 从右到左来组合多个函数。
  2. 当需要把多个 store 增强器 依次执行的时候,需要用到它

</>复制代码

  1. import { createStore, applyMiddleware, compose } from "redux"
  2. import thunk from "redux-thunk"
  3. import DevTools from "./containers/DevTools"
  4. import reducer from "../reducers"
  5. const store = createStore(
  6. reducer,
  7. compose(
  8. applyMiddleware(thunk),
  9. DevTools.instrument()
  10. )
  11. )

实现:
compose(fn1, fn2, fn3)
fn1(fn2(fn3))

</>复制代码

  1. export function compose(...funcs) {
  2. if(funcs.length == 0) {
  3. return arg => arg
  4. }
  5. if(funcs.length == 1) {
  6. return funcs[0]
  7. }
  8. return funcs.reduce((ret,item) => (...args) => ret(item(...args)))
  9. }

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/98479.html

相关文章

  • React进阶系列手写实现react-redux api

    简介:简单实现react-redux基础api react-redux api回顾 把store放在context里,所有子组件可以直接拿到store数据 使组件层级中的 connect() 方法都能够获得 Redux store 根组件应该嵌套在 中 ReactDOM.render( , rootEl ) ReactDOM.render( ...

    刘玉平 评论0 收藏0
  • 前端进阶资源整理

    摘要:前端进阶进阶构建项目一配置最佳实践状态管理之痛点分析与改良开发中所谓状态浅析从时间旅行的乌托邦,看状态管理的设计误区使用更好地处理数据爱彼迎房源详情页中的性能优化从零开始,在中构建时间旅行式调试用轻松管理复杂状态如何把业务逻辑这个故事讲好和 前端进阶 webpack webpack进阶构建项目(一) Webpack 4 配置最佳实践 react Redux状态管理之痛点、分析与...

    BlackMass 评论0 收藏0
  • 新上课程推荐:《React Hooks 案例详解(React 进阶必备)》

    摘要:课程制作和案例制作都经过精心编排。对于开发者意义重大,希望对有需要的开发者有所帮助。是从提案转为正式加入的新特性。并不需要用继承,而是推荐用嵌套。大型项目中模块化与功能解耦困难。从而更加易于复用和独立测试。但使用会减少这种几率。 showImg(https://segmentfault.com/img/bVbpNRZ?w=1920&h=1080); 讲师简介 曾任职中软军队事业部,参与...

    Lin_YT 评论0 收藏0
  • 2018大厂高级前端面试题汇总

    摘要:面试的公司分别是阿里网易滴滴今日头条有赞挖财沪江饿了么携程喜马拉雅兑吧微医寺库宝宝树海康威视蘑菇街酷家乐百分点和海风教育。 (关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导) 本人于7-8月开始准备面试,过五关斩六将,最终抱得网易归,深深感受到高级前端面试的套路。以下是自己整理的面试题汇总,不敢藏私,统统贡献出来。 面试的公司分...

    zzir 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<