国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Redux 入坑進階 - 源碼解析

BothEyes1993 / 423人閱讀

摘要:否則的話,認為只是一個普通的,將通過也就是進一步分發。在本組件內的應用傳遞給子組件源碼解析期待一個作為傳入,里面是如果只是傳入一個,則通過返回被綁定到的函數遍歷并通過分發綁定至將其聲明為的屬性之一接收的作為傳入。

原文鏈接:https://github.com/ecmadao/Co...
轉載請注明出處

本文不涉及redux的使用方法,因此可能更適合使用過redux的玩家翻閱?

預熱

redux 函數內部包含了大量柯里化函數以及代碼組合思想

柯里化函數(curry

通俗的來講,可以用一句話概括柯里化函數:返回函數的函數

// example
const funcA = (a) => {
  return const funcB = (b) => {
    return a + b
  }
};

上述的funcA函數接收一個參數,并返回同樣接收一個參數的funcB函數。

柯里化函數有什么好處呢?

避免了給一個函數傳入大量的參數--我們可以通過柯里化來構建類似上例的函數嵌套,將參數的代入分離開,更有利于調試

降低耦合度和代碼冗余,便于復用

舉個栗子:

// 已知listA, listB兩個Array,都由int組成,需要篩選出兩個Array的交集
const listA = [1, 2, 3, 4, 5];
const listB = [2, 3, 4];

const checkIfDataExist = (list) => {
  return (target) => {
    return list.some(value => value === target)
  };
};
// 調用一次checkIfDataExist函數,并將listA作為參數傳入,來構建一個新的函數。
// 而新函數的作用則是:檢查傳入的參數是否存在于listA里
const ifDataExist = checkIfDataExist(listA);

// 使用新函數來對listB里的每一個元素進行篩選
const intersectionList = listB.filter(value => ifDataExist(value));
console.log(intersectionList); // [2, 3, 4]
代碼組合(compose

代碼組合就像是數學中的結合律:

const compose = (f, g) => {
  return (x) => {
    return f(g(x));
  };
};
// 還可以再簡潔點
const compose = (f, g) => (x) => f(g(x));

通過這樣函數之間的組合,可以大大增加可讀性,效果遠大于嵌套一大堆的函數調用,并且我們可以隨意更改函數的調用順序

Redux combineReducers
// 回顧一下combineReducers的使用格式

// 兩個reducer
const todos = (state = INIT.todos, action) => {
  // ....
};
const filterStatus = (state = INIT.filterStatus, action) => {
  // ...
};

const appReducer = combineReducers({
  todos,
  filterStatus
});

還記得combineReducers的黑魔法嗎?即:

傳入的Object參數中,對象的keyvalue所代表的reducer function同名

各個reducer function的名稱和需要傳入該reducer的state參數同名

源碼標注解讀(省略部分):

export default function combineReducers(reducers) {
  // 第一次篩選,參數reducers為Object
  // 篩選掉reducers中不是function的鍵值對
  var reducerKeys = Object.keys(reducers);
  var finalReducers = {}
  for (var i = 0; i < reducerKeys.length; i++) {
    var key = reducerKeys[i];
    if (typeof reducers[key] === "function") {
      finalReducers[key] = reducers[key]
    }
  }

  var finalReducerKeys = Object.keys(finalReducers)

  // 二次篩選,判斷reducer中傳入的值是否合法(!== undefined)
  // 獲取篩選完之后的所有key
  var sanityError
  try {
    // assertReducerSanity函數用于遍歷finalReducers中的reducer,檢查傳入reducer的state是否合法
    assertReducerSanity(finalReducers)
  } catch (e) {
    sanityError = e
  }
  
  // 返回一個function。該方法接收state和action作為參數
  return function combination(state = {}, action) {
    // 如果之前的判斷reducers中有不法值,則拋出錯誤
    if (sanityError) {
      throw sanityError
    }
    // 如果不是production環境則拋出warning
    if (process.env.NODE_ENV !== "production") {
      var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action)
      if (warningMessage) {
        warning(warningMessage)
      }
    }

    var hasChanged = false
    var nextState = {}
    // 遍歷所有的key和reducer,分別將reducer對應的key所代表的state,代入到reducer中進行函數調用
    for (var i = 0; i < finalReducerKeys.length; i++) {
      var key = finalReducerKeys[i]
      var reducer = finalReducers[key]
      // 這也就是為什么說combineReducers黑魔法--要求傳入的Object參數中,reducer function的名稱和要和state同名的原因
      var previousStateForKey = state[key]
      var nextStateForKey = reducer(previousStateForKey, action)
      // 如果reducer返回undefined則拋出錯誤
      if (typeof nextStateForKey === "undefined") {
        var errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      // 將reducer返回的值填入nextState
      nextState[key] = nextStateForKey
      // 如果任一state有更新則hasChanged為true
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

// 檢查傳入reducer的state是否合法
function assertReducerSanity(reducers) {
  Object.keys(reducers).forEach(key => {
    var reducer = reducers[key]
    // 遍歷全部reducer,并給它傳入(undefined, action)
    // 當第一個參數傳入undefined時,則為各個reducer定義的默認參數
    var initialState = reducer(undefined, { type: ActionTypes.INIT })
    
    // ActionTypes.INIT幾乎不會被定義,所以會通過switch的default返回reducer的默認參數。如果沒有指定默認參數,則返回undefined,拋出錯誤
    if (typeof initialState === "undefined") {
      throw new Error(
        `Reducer "${key}" returned undefined during initialization. ` +
        `If the state passed to the reducer is undefined, you must ` +
        `explicitly return the initial state. The initial state may ` +
        `not be undefined.`
      )
    }

    var type = "@@redux/PROBE_UNKNOWN_ACTION_" + Math.random().toString(36).substring(7).split("").join(".")
    if (typeof reducer(undefined, { type }) === "undefined") {
      throw new Error(
        `Reducer "${key}" returned undefined when probed with a random type. ` +
        `Don"t try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
        `namespace. They are considered private. Instead, you must return the ` +
        `current state for any unknown actions, unless it is undefined, ` +
        `in which case you must return the initial state, regardless of the ` +
        `action type. The initial state may not be undefined.`
      )
    }
  })
}
createStore
// 回顧下使用方法
const store = createStore(reducers, state, enhance);

源碼標注解讀(省略部分):

// 對于未知的action.type,reducer必須返回默認的參數state。這個ActionTypes.INIT就可以用來監測當reducer傳入未知type的action時,返回的state是否合法
export var ActionTypes = {
  INIT: "@@redux/INIT"
}

export default function createStore(reducer, initialState, enhancer) {
  // 檢查你的state和enhance參數有沒有傳反
  if (typeof initialState === "function" && typeof enhancer === "undefined") {
    enhancer = initialState
    initialState = undefined
  }
  // 如果有傳入合法的enhance,則通過enhancer再調用一次createStore
  if (typeof enhancer !== "undefined") {
    if (typeof enhancer !== "function") {
      throw new Error("Expected the enhancer to be a function.")
    }
    return enhancer(createStore)(reducer, initialState)
  }

  if (typeof reducer !== "function") {
    throw new Error("Expected the reducer to be a function.")
  }

  var currentReducer = reducer
  var currentState = initialState
  var currentListeners = []
  var nextListeners = currentListeners
  var isDispatching = false // 是否正在分發事件

  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  // 我們在action middleware中經常使用的getState()方法,返回當前state
  function getState() {
    return currentState
  }

  // 注冊listener,同時返回一個取消事件注冊的方法。當調用store.dispatch的時候調用listener
  function subscribe(listener) {
    if (typeof listener !== "function") {
      throw new Error("Expected listener to be a function.")
    }

    var isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      isSubscribed = false
      // 從nextListeners中去除掉當前listener
      ensureCanMutateNextListeners()
      var index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

  // dispatch方法接收的action是個對象,而不是方法。
  // 這個對象實際上就是我們自定義action的返回值,因為dispatch的時候,已經調用過我們的自定義action了,比如 dispatch(addTodo())
  function dispatch(action) {
    if (!isPlainObject(action)) {
      throw new Error(
        "Actions must be plain objects. " +
        "Use custom middleware for async actions."
      )
    }

    if (typeof action.type === "undefined") {
      throw new Error(
        "Actions may not have an undefined "type" property. " +
        "Have you misspelled a constant?"
      )
    }
    // 調用dispatch的時候只能一個個調用,通過dispatch判斷調用的狀態
    if (isDispatching) {
      throw new Error("Reducers may not dispatch actions.")
    }

    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }
    // 遍歷調用各個linster
    var listeners = currentListeners = nextListeners
    for (var i = 0; i < listeners.length; i++) {
      listeners[i]()
    }

    return action
  }
  // Replaces the reducer currently used by the store to calculate the state.
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== "function") {
      throw new Error("Expected the nextReducer to be a function.")
    }

    currentReducer = nextReducer
    dispatch({ type: ActionTypes.INIT })
  }
  // 當create store的時候,reducer會接受一個type為ActionTypes.INIT的action,使reducer返回他們默認的state,這樣可以快速的形成默認的state的結構
  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer
  }
}
thunkMiddleware

源碼及其簡單簡直給跪...

// 返回以 dispatch 和 getState 作為參數的action
export default function thunkMiddleware({ dispatch, getState }) {
  return next => action => {
    if (typeof action === "function") {
      return action(dispatch, getState);
    }

    return next(action);
  };
}
applyMiddleware

先復習下用法:

// usage
import {createStore, applyMiddleware} from "redux";
import thunkMiddleware from "redux-thunk";

const store = createStore(
      reducers,
      state,
      applyMiddleware(thunkMiddleware)
);

applyMiddleware首先接收thunkMiddleware作為參數,兩者組合成為一個新的函數(enhance),之后在createStore內部,因為enhance的存在,將會變成返回enhancer(createStore)(reducer, initialState)

源碼標注解讀(省略部分):

// 定義一個代碼組合的方法
// 傳入一些function作為參數,返回其鏈式調用的形態。例如,
// compose(f, g, h) 最終返回 (...args) => f(g(h(...args)))
export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  } else {
    const last = funcs[funcs.length - 1]
    const rest = funcs.slice(0, -1)
    return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
  }
}

export default function applyMiddleware(...middlewares) {
  // 最終返回一個以createStore為參數的匿名函數
  // 這個函數返回另一個以reducer, initialState, enhancer為參數的匿名函數
  return (createStore) => (reducer, initialState, enhancer) => {
    var store = createStore(reducer, initialState, enhancer)
    var dispatch
    var chain = []

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    // 每個 middleware 都以 middlewareAPI 作為參數進行注入,返回一個新的鏈。此時的返回值相當于調用 thunkMiddleware 返回的函數: (next) => (action) => {} ,接收一個next作為其參數
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    // 并將鏈代入進 compose 組成一個函數的調用鏈
    // compose(...chain) 返回形如(...args) => f(g(h(...args))),f/g/h都是chain中的函數對象。
    // 在目前只有 thunkMiddleware 作為 middlewares 參數的情況下,將返回 (next) => (action) => {}
    // 之后以 store.dispatch 作為參數進行注入
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

一臉懵逼?沒關系,來結合實際使用總結一下:

當我們搭配redux-thunk這個庫的時候,在redux配合components時,通常這么寫

import thunkMiddleware from "redux-thunk";
import { createStore, applyMiddleware, combineReducer } from "redux";
import * as reducers from "./reducers.js";

const appReducer = combineReducer(reducers);
const store = createStore(appReducer, initialState, applyMiddleware(thunkMiddleware));

還記得當createStore收到的參數中有enhance時會怎么做嗎?

// createStore.js
if (typeof enhancer !== "undefined") {
  if (typeof enhancer !== "function") {
    throw new Error("Expected the enhancer to be a function.")
  }
  return enhancer(createStore)(reducer, initialState)
}

也就是說,會變成下面的情況

applyMiddleware(thunkMiddleware)(createStore)(reducer, initialState)

applyMiddleware(thunkMiddleware)
applyMiddleware接收thunkMiddleware作為參數,返回形如(createStore) => (reducer, initialState, enhancer) => {}的函數。

applyMiddleware(thunkMiddleware)(createStore)
以 createStore 作為參數,調用上一步返回的函數(reducer, initialState, enhancer) => {}

applyMiddleware(thunkMiddleware)(createStore)(reducer, initialState)
以(reducer, initialState)為參數進行調用。

在這個函數內部,thunkMiddleware被調用,其作用是監測typefunctionaction

因此,如果dispatchaction返回的是一個function,則證明是中間件,則將(dispatch, getState)作為參數代入其中,進行action 內部下一步的操作。否則的話,認為只是一個普通的action,將通過next(也就是dispatch)進一步分發。

也就是說,applyMiddleware(thunkMiddleware)作為enhance,最終起了這樣的作用:

dispatch調用的action(例如,dispatch(addNewTodo(todo)))進行檢查,如果action在第一次調用之后返回的是function,則將(dispatch, getState)作為參數注入到action返回的方法中,否則就正常對action進行分發,這樣一來我們的中間件就完成嘍~

因此,當action內部需要獲取state,或者需要進行異步操作,在操作完成之后進行事件調用分發的話,我們就可以讓action 返回一個以(dispatch, getState)為參數的function而不是通常的Object,enhance就會對其進行檢測以便正確的處理。

bindActionCreator

這個方法感覺比較少見,我個人也很少用到

在傳統寫法下,當我們要把 state 和 action 注入到子組件中時,一般會這么做:

import { connect } from "react-redux";
import {addTodo, deleteTodo} from "./action.js";

class TodoComponect extends Component {
  render() {
    return (
      
    )
  }
}

function mapStateToProps(state) {
  return {
    state
  }
}
function mapDispatchToProps(dispatch) {
  return {
    deleteTodo: (id) => {
      dispatch(deleteTodo(id));
    },
    addTodo: (todo) => {
      dispatch(addTodo(todo));
    }
  }
}
export default connect(mapStateToProps, mapDispatchToProps)(TodoComponect);

使用bindActionCreators可以把 action 轉為同名 key 的對象,但使用 dispatch 把每個 action 包圍起來調用

惟一使用 bindActionCreators 的場景是當你需要把 action creator 往下傳到一個組件上,卻不想讓這個組件覺察到 Redux 的存在,而且不希望把 Redux store 或 dispatch 傳給它。

import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import {addTodo, deleteTodo} as TodoActions from "./action.js";

class TodoComponect extends React.Component {
  
  // 在本組件內的應用
  addTodo(todo) {
    let action = TodoActions.addTodo(todo);
    this.props.dispatch(action);
  }
  
  deleteTodo(id) {
    let action = TodoActions.deleteTodo(id);
    this.props.dispatch(action);
  }
  
  render() {
    let dispatch = this.props.dispatch;
    // 傳遞給子組件
    let boundActionCreators = bindActionCreators(TodoActions, dispatch);
    return (
      
    )
  }
}

function mapStateToProps(state) {
  return {
    state
  }
}
export default connect(mapStateToProps)(TodoComponect)
bindActionCreator源碼解析
function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}

// bindActionCreators期待一個Object作為actionCreators傳入,里面是 key: action
export default function bindActionCreators(actionCreators, dispatch) {
  // 如果只是傳入一個action,則通過bindActionCreator返回被綁定到dispatch的函數
  if (typeof actionCreators === "function") {
    return bindActionCreator(actionCreators, dispatch)
  }

  if (typeof actionCreators !== "object" || actionCreators === null) {
    throw new Error(
      `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? "null" : typeof actionCreators}. ` +
      `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
    )
  }

  // 遍歷并通過bindActionCreator分發綁定至dispatch
  var keys = Object.keys(actionCreators)
  var boundActionCreators = {}
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i]
    var actionCreator = actionCreators[key]
    if (typeof actionCreator === "function") {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}
react-redux Provider
export default class Provider extends Component {
  getChildContext() {
    // 將其聲明為 context 的屬性之一
    return { store: this.store }
  }

  constructor(props, context) {
    super(props, context)
    // 接收 redux 的 store 作為 props
    this.store = props.store
  }

  render() {
    return Children.only(this.props.children)
  }
}

if (process.env.NODE_ENV !== "production") {
  Provider.prototype.componentWillReceiveProps = function (nextProps) {
    const { store } = this
    const { store: nextStore } = nextProps

    if (store !== nextStore) {
      warnAboutReceivingStore()
    }
  }
}

Provider.propTypes = {
  store: storeShape.isRequired,
  children: PropTypes.element.isRequired
}
Provider.childContextTypes = {
  store: storeShape.isRequired
}
connect

傳入mapStateToProps,mapDispatchToProps,mergeProps,options。
首先獲取傳入的參數,如果沒有則以默認值代替

const defaultMapStateToProps = state => ({}) // eslint-disable-line no-unused-vars
const defaultMapDispatchToProps = dispatch => ({ dispatch })
const { pure = true, withRef = false } = options

之后,通過

const finalMergeProps = mergeProps || defaultMergeProps

選擇合并stateProps,dispatchProps,parentProps的方式,默認的合并方式 defaultMergeProps 為:

const defaultMergeProps = (stateProps, dispatchProps, parentProps) => ({
  ...parentProps,
  ...stateProps,
  ...dispatchProps
})

返回一個以 Component 作為參數的函數。在這個函數內部,生成了一個叫做Connect的 Component

// ...
  return function wrapWithConnect(WrappedComponent) {
    const connectDisplayName = `Connect(${getDisplayName(WrappedComponent)})`
    // 檢查參數合法性
    function checkStateShape(props, methodName) {}
    // 合并props
    function computeMergedProps(stateProps, dispatchProps, parentProps) {
      const mergedProps = finalMergeProps(stateProps, dispatchProps, parentProps)
      if (process.env.NODE_ENV !== "production") {
        checkStateShape(mergedProps, "mergeProps")
      }
      return mergedProps
    }
    
    // start of Connect
    class Connect extends Component {
      constructor(props, context) {
        super(props, context);
        this.store = props.store || context.store
        
        const storeState = this.store.getState()
        this.state = { storeState }
        this.clearCache()
      }
      
      computeStateProps(store, props) {
        // 調用configureFinalMapState,使用傳入的mapStateToProps方法(或默認方法),將state map進props
      }
      configureFinalMapState(store, props) {}
      
      computeDispatchProps(store, props) {
        // 調用configureFinalMapDispatch,使用傳入的mapDispatchToProps方法(或默認方法),將action使用dispatch封裝map進props
      }
      configureFinalMapDispatch(store, props) {}
      
      // 判斷是否更新props
      updateStatePropsIfNeeded() {}
      updateDispatchPropsIfNeeded() {}
      updateMergedPropsIfNeeded() {}
      
      componentDidMount() {
        // 內部調用this.store.subscribe(this.handleChange.bind(this))
        this.trySubscribe()
      }
      handleChange() {
        const storeState = this.store.getState()
        const prevStoreState = this.state.storeState
        // 對數據進行監聽,發送改變時調用
        this.setState({ storeState })
      }
      
      // 取消監聽,清除緩存
      componentWillUnmount() {
        this.tryUnsubscribe()
        this.clearCache()
      }
      
      render() {
        this.renderedElement = createElement(WrappedComponent,
            this.mergedProps
        )
        return this.renderedElement
      }
    }
    // end of Connect
    
    Connect.displayName = connectDisplayName
    Connect.WrappedComponent = WrappedComponent
    Connect.contextTypes = {
      store: storeShape
    }
    Connect.propTypes = {
      store: storeShape
    }
    
    return hoistStatics(Connect, WrappedComponent)
  }
// ...

我們看見,在connect的最后,返回了使用hoistStatics包裝的ConnectWrappedComponent

hoistStatics是什么鬼?為什么使用它?

Copies non-react specific statics from a child component to a parent component. Similar to Object.assign, but with React static keywords blacklisted from being overridden.

也就是說,它類似于Object.assign,作用是將子組件中的 static 方法復制進父組件,但不會覆蓋組件中的關鍵字方法(如 componentDidMount)

import hoistNonReactStatic from "hoist-non-react-statics";

hoistNonReactStatic(targetComponent, sourceComponent);

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/80426.html

相關文章

  • 2017文章總結

    摘要:歡迎來我的個人站點性能優化其他優化瀏覽器關鍵渲染路徑開啟性能優化之旅高性能滾動及頁面渲染優化理論寫法對壓縮率的影響唯快不破應用的個優化步驟進階鵝廠大神用直出實現網頁瞬開緩存網頁性能管理詳解寫給后端程序員的緩存原理介紹年底補課緩存機制優化動 歡迎來我的個人站點 性能優化 其他 優化瀏覽器關鍵渲染路徑 - 開啟性能優化之旅 高性能滾動 scroll 及頁面渲染優化 理論 | HTML寫法...

    dailybird 評論0 收藏0
  • 2017文章總結

    摘要:歡迎來我的個人站點性能優化其他優化瀏覽器關鍵渲染路徑開啟性能優化之旅高性能滾動及頁面渲染優化理論寫法對壓縮率的影響唯快不破應用的個優化步驟進階鵝廠大神用直出實現網頁瞬開緩存網頁性能管理詳解寫給后端程序員的緩存原理介紹年底補課緩存機制優化動 歡迎來我的個人站點 性能優化 其他 優化瀏覽器關鍵渲染路徑 - 開啟性能優化之旅 高性能滾動 scroll 及頁面渲染優化 理論 | HTML寫法...

    hellowoody 評論0 收藏0
  • 2017文章總結

    摘要:歡迎來我的個人站點性能優化其他優化瀏覽器關鍵渲染路徑開啟性能優化之旅高性能滾動及頁面渲染優化理論寫法對壓縮率的影響唯快不破應用的個優化步驟進階鵝廠大神用直出實現網頁瞬開緩存網頁性能管理詳解寫給后端程序員的緩存原理介紹年底補課緩存機制優化動 歡迎來我的個人站點 性能優化 其他 優化瀏覽器關鍵渲染路徑 - 開啟性能優化之旅 高性能滾動 scroll 及頁面渲染優化 理論 | HTML寫法...

    wwolf 評論0 收藏0
  • SegmentFault 技術周刊 Vol.16 - 淺入淺出 JavaScript 函數式編程

    摘要:函數式編程,一看這個詞,簡直就是學院派的典范。所以這期周刊,我們就重點引入的函數式編程,淺入淺出,一窺函數式編程的思想,可能讓你對編程語言的理解更加融會貫通一些。但從根本上來說,函數式編程就是關于如使用通用的可復用函數進行組合編程。 showImg(https://segmentfault.com/img/bVGQuc); 函數式編程(Functional Programming),一...

    csRyan 評論0 收藏0
  • 前端進階資源整理

    摘要:前端進階進階構建項目一配置最佳實踐狀態管理之痛點分析與改良開發中所謂狀態淺析從時間旅行的烏托邦,看狀態管理的設計誤區使用更好地處理數據愛彼迎房源詳情頁中的性能優化從零開始,在中構建時間旅行式調試用輕松管理復雜狀態如何把業務邏輯這個故事講好和 前端進階 webpack webpack進階構建項目(一) Webpack 4 配置最佳實踐 react Redux狀態管理之痛點、分析與...

    BlackMass 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<