日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Redux其实很简单(原理篇)

發布時間:2025/3/19 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Redux其实很简单(原理篇) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在上一篇文章中,我們通過一個示例頁面,了解到Redux的使用方法以及各個功能模塊的作用。如果還不清楚Redux如何使用,可以先看看Redux其實很簡單(示例篇),然后再來看本文,理解起來會更加輕松。

那么在這一篇文章中,筆者將帶大家編寫一個完整的Redux,深度剖析Redux的方方面面,讀完本篇文章后,大家對Redux會有一個深刻的認識。

核心API

這套代碼是筆者閱讀完Redux源碼,理解其設計思路后,自行總結編寫的一套代碼,API的設計遵循與原始一致的原則,省略掉了一些不必要的API。

createStore

這個方法是Redux核心中的核心,它將所有其他的功能連接在一起,暴露操作的API供開發者調用。




const INIT = '@@redux/INIT_' + Math.random().toString(36).substring(7)export default function createStore (reducer, initialState, enhancer) {if (typeof initialState === 'function') {enhancer = initialStateinitialState = undefined}let state = initialStateconst listeners = []const store = {getState () {return state},dispatch (action) {if (action && action.type) {state = reducer(state, action)listeners.forEach(listener => listener())}},subscribe (listener) {if (typeof listener === 'function') {listeners.push(listener)}}}if (typeof initialState === 'undefined') {store.dispatch({ type: INIT })}if (typeof enhancer === 'function') {return enhancer(store)}return store }

在初始化時,createStore會主動觸發一次dispach,它的action.type是系統內置的INIT,所以在reducer中不會匹配到任何開發者自定義的action.type,它走的是switch中default的邏輯,目的是為了得到初始化的狀態。

當然我們也可以手動指定initialState,筆者在這里做了一層判斷,當initialState沒有定義時,我們才會dispatch,而在源碼中是都會執行一次dispatch,筆者認為沒有必要,這是一次多余的操作。因為這個時候,監聽流中沒有注冊函數,走了一遍reducer中的default邏輯,得到新的state和initialState是一樣的。

第三個參數enhancer只有在使用中間件時才會用到,通常情況下我們搭配applyMiddleware來使用,它可以增強dispatch的功能,如常用的logger和thunk,都是增強了dispatch的功能。

同時createStore會返回一些操作API,包括:

  • getState:獲取當前的state值
  • dispatch:觸發reducer并執行listeners中的每一個方法
  • subscribe:將方法注冊到listeners中,通過dispatch來觸發

applyMiddleware

這個方法通過中間件來增強dispatch的功能。

在寫代碼前,我們先來了解一下函數的合成,這對后續理解applyMiddleware的原理大有裨益。

函數的合成

如果一個值要經過多個函數,才能變成另外一個值,就可以把所有中間步驟合并成一個函數,這叫做函數的合成(compose)

舉個例子


function add (a) {return function (b) {return a + b} }// 得到合成后的方法 let add6 = compose(add(1), add(2), add(3))add6(10) // 16
下面我們通過一個非常巧妙的方法來寫一個函數的合成(compose)。

export 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))) }

上述代碼巧妙的地方在于:通過數組的reduce方法,將兩個方法合成一個方法,然后用這個合成的方法再去和下一個方法合成,直到結束,這樣我們就得到了一個所有方法的合成函數。

有了這個基礎,applyMiddleware就會變得非常簡單。


import { compose } from './utils'export default function applyMiddleware (...middlewares) {return store => {const chains = middlewares.map(middleware => middleware(store))store.dispatch = compose(...chains)(store.dispatch)return store} } 光看這段代碼可能有點難懂,我們配合中間件的代碼結構來幫助理解

function middleware (store) {return function f1 (dispatch) {return function f2 (action) {// do somethingdispatch(action)// do something}} }

可以看出,chains是函數f1的數組,通過compose將所欲f1合并成一個函數,暫且稱之為F1,然后我們將原始dispatch傳入F1,經過f2函數一層一層地改造后,我們得到了一個新的dispatch方法,這個過程和Koa的中間件模型(洋蔥模型)原理是一樣的。

為了方便大家理解,我們再來舉個例子,有以下兩個中間件



function middleware1 (store) {return function f1 (dispatch) {return function f2 (action) {console.log(1)dispatch(action)console.log(1)}} }function middleware2 (store) {return function f1 (dispatch) {return function f2 (action) {console.log(2)dispatch(action)console.log(2)}} }// applyMiddleware(middleware1, middleware2)

大家猜一猜以上的log輸出順序是怎樣的?

好了,答案揭曉:1, 2, (原始dispatch), 2, 1。

為什么會這樣呢?因為middleware2接收的dispatch是最原始的,而middleware1接收的dispatch是經過middleware1改造后的,我把它們寫成如下的樣子,大家應該就清楚了。


console.log(1)/* middleware1返回給middleware2的dispatch */ console.log(2) dispatch(action) console.log(2) /* end */console.log(1)

三個或三個以上的中間件,其原理也是如此。

至此,最復雜最難理解的中間件已經講解完畢。

combineReducers

由于Redux是單一狀態流管理的模式,因此如果有多個reducer,我們需要合并一下,這塊的邏輯比較簡單,直接上代碼。


export default function combineReducers (reducers) {const availableKeys = []const availableReducers = {}Object.keys(reducers).forEach(key => {if (typeof reducers[key] === 'function') {availableKeys.push(key)availableReducers[key] = reducers[key]}})return (state = {}, action) => {const nextState = {}let hasChanged = falseavailableKeys.forEach(key => {nextState[key] = availableReducers[key](state[key], action)if (!hasChanged) {hasChanged = state[key] !== nextState[key]}})return hasChanged ? nextState : state} }

combineReucers將單個reducer塞到一個對象中,每個reducer對應一個唯一鍵值,單個reducer狀態改變時,對應鍵值的值也會改變,然后返回整個state。

bindActionCreators

這個方法就是將我們的action和dispatch連接起來。



function bindActionCreator (actionCreator, dispatch) {return function () {dispatch(actionCreator.apply(this, arguments))} }export default function bindActionCreators (actionCreators, dispatch) {if (typeof actionCreators === 'function') {return bindActionCreator(actionCreators, dispatch)}const boundActionCreators = {}Object.keys(actionCreators).forEach(key => {let actionCreator = actionCreators[key]if (typeof actionCreator === 'function') {boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)}})return boundActionCreators }

它返回一個方法集合,直接調用來觸發dispatch。

中間件

在自己動手編寫中間件時,你一定會驚奇的發現,原來這么好用的中間件代碼竟然只有寥寥數行,卻可以實現這么強大的功能。

logger


function getFormatTime () {const date = new Date()return date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds() + ' ' + date.getMilliseconds() }export default function logger ({ getState }) {return next => action => {/* eslint-disable no-console */console.group(`%caction %c${action.type} %c${getFormatTime()}`, 'color: gray; font-weight: lighter;', 'inherit', 'color: gray; font-weight: lighter;')// console.time('time')console.log(`%cprev state`, 'color: #9E9E9E; font-weight: bold;', getState())console.log(`%caction `, 'color: #03A9F4; font-weight: bold;', action)next(action)console.log(`%cnext state`, 'color: #4CAF50; font-weight: bold;', getState())// console.timeEnd('time')console.groupEnd()} }

thunk


export default function thunk ({ getState }) {return next => action => {if (typeof action === 'function') {action(next, getState)} else {next(action)}} }

這里要注意的一點是,中間件是有執行順序的。像在這里,第一個參數是thunk,然后才是logger,因為假如logger在前,那么這個時候action可能是一個包含異步操作的方法,不能正常輸出action的信息。

心得體會

到了這里,關于Redux的方方面面都已經講完了,希望大家看完能夠有所收獲。

但是筆者其實還有一個擔憂:每一次dispatch都會重新渲染整個視圖,雖然React是在虛擬DOM上進行diff,然后定向渲染需要更新的真實DOM,但是我們知道,一般使用Redux的場景都是中大型應用,管理龐大的狀態數據,這個時候整個虛擬DOM進行diff可能會產生比較明顯的性能損耗(diff過程實際上是對象和對象的各個字段挨個比較,如果數據達到一定量級,雖然沒有操作真實DOM,也可能產生可觀的性能損耗,在小型應用中,由于數據較少,因此diff的性能損耗可以忽略不計)。


原文發布時間為:2018年06月28日
原文作者:連城
本文來源: 掘金?如需轉載請聯系原作者

總結

以上是生活随笔為你收集整理的Redux其实很简单(原理篇)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。