Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to parse the source code of Redux

2025-03-27 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

How to parse the source code of Redux, many novices are not very clear about this, in order to help you solve this problem, the following editor will explain in detail for you, people with this need can come to learn, I hope you can get something.

Preheating

The redux function contains a large number of Corey functions and code combination ideas.

Corialization function (curry)

In popular terms, you can generalize the Coriarization function in one sentence: the function that returns the function.

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

The above funcA function takes an argument and returns a funcB function that also receives an argument.

What are the benefits of the Corialization function?

Avoid passing a large number of parameters to a function-- we can build a function nesting similar to the above example by Corialization, and separate the substitution of parameters, which is more conducive to debugging

Reduce coupling and code redundancy and facilitate reuse

Take a chestnut:

/ / it is known that both listA and listB Array are composed of int. You need to filter out the intersection of two Array const listA = [1, 2, 3, 4, 5]; const listB = [2, 3, 4]; const checkIfDataExist = (list) = > {return (target) = > {return list.some (value = > value = target)}; / / call the checkIfDataExist function once and pass listA as an argument to build a new function. / / the purpose of the new function is to check whether the passed parameters exist in listA const ifDataExist = checkIfDataExist (listA); / / use the new function to filter every element in listB const intersectionList = listB.filter (value = > ifDataExist (value)); console.log (intersectionList); / / [2,3,4]

Code combination (compose)

Code combination is like the law of association in mathematics:

Const compose = (f, g) = > {return (x) = > {return f (g (x));};}; / / you can also simplify const compose = (f, g) = > (x) = > f (g (x))

Through this combination of functions, the readability can be greatly increased, and the effect is far greater than that of a large number of nested function calls, and we can change the order of function calls at will.

Redux

CombineReducers

/ / Review the format of combineReducers / / two reducer const todos = (state = INIT.todos, action) = > {/ /.... }; const filterStatus = (state = INIT.filterStatus, action) = > {/ /...}; const appReducer = combineReducers ({todos, filterStatus})

Remember combineReducers's dark magic? That is:

In the passed Object parameter, the key of the object has the same name as the reducer function represented by value

The name of each reducer function is the same as the state parameter that needs to be passed into the reducer

Interpretation of source code annotations (omitted):

Export default function combineReducers (reducers) {/ / * filter times. Parameter reducers is Object / / filter out the key-value pair var reducerKeys = Object.keys (reducers) that is not function in 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 =>

If no default parameter is specified, undefined is returned and the error if (typeof initialState = = 'undefined') {throw new Error (`Reducer "${key}" returned undefined during initialization.) is thrown. `+ `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

/ / Review the usage method const store = createStore (reducers, state, enhance)

Interpretation of source code annotations (omitted):

/ / the default parameter state must be returned for unknown action.type,reducer. This ActionTypes.INIT can be used to monitor whether the returned state is legal when reducer passes the action of an unknown type. Export var ActionTypes = {INIT:'@ redux/INIT'} export default function createStore (reducer, initialState, enhancer) {/ / check whether your state and enhance parameters are passed in reverse if (typeof initialState = = 'function' & & typeof enhancer = =' undefined') {enhancer = initialState initialState = undefined} / / if there is a legitimate enhance Then call createStore if (typeof enhancer! = = 'undefined') {if (typeof enhancer! = =' function') {throw new Error ('Expected the enhancer to be a function.')} return enhancer (createStore) (reducer) again via enhancer 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 / / whether the event function ensureCanMutateNextListeners () {if (nextListeners = = currentListeners) {nextListeners = currentListeners.slice ()} / / We are in the Returns the current state function getState () {return currentState} / / register listener and returns a method to unregister the event. When calling store.dispatch, call 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 / / from nextListeners Remove the current listener ensureCanMutateNextListeners () var index = nextListeners.indexOf (listener) nextListeners.splice (index) 1) the action received by the}} / / dispatch method is an object / / this object is actually the return value of our custom action, because our custom action has already been called during dispatch, such as 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?')} / / can only be called one by one when calling dispatch Determine the status of the call by dispatch if (isDispatching) {throw new Error ('Reducers may not dispatch actions.')} try {isDispatching = true currentState = currentReducer (currentState, action)} finally {isDispatching = false} / / call each 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

Review the usage first:

/ / usage import {createStore, applyMiddleware} from 'redux'; import thunkMiddleware from' redux-thunk'; const store = createStore (reducers, state, applyMiddleware (thunkMiddleware))

ApplyMiddleware first takes thunkMiddleware as an argument, and the two are combined to form a new function (enhance). Then inside createStore, because of the existence of enhance, it will return enhancer (createStore) (reducer, initialState).

Interpretation of source code annotations (omitted):

/ / define a code combination method / / pass in some function as parameters to return the form of its chained call. For example, / / compose (f, g, h) finally returns (... 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) {/ / finally returns an anonymous function that takes createStore / / this function returns another anonymous function return (createStore) = > (reducer, initialState, enhancer) > (reducer, initialState, enhancer) > {var store = createStore (reducer, initialState, enhancer) var dispatch var chain = [] var middlewareAPI = {getState: store.getState. Dispatch: (action) = > dispatch (action)} / / each middleware is injected with middlewareAPI as a parameter Returns a new chain. In this case, the return value is equivalent to the function returned by calling thunkMiddleware: (next) = > (action) = > {}, receiving a next as its parameter chain = middlewares.map (middleware = > middleware (middlewareAPI)) / / and substituting the chain into the call chain / / compose (... chain) of compose to form a function. F/g/h is returned as (... args) = > f (h (. ARGs)), and f/g/h is a function object in chain. / / if only thunkMiddleware is used as the middlewares parameter, (next) = > (action) = > {} / / is returned and then dispatch = compose (... chain) (store.dispatch) return {... store, dispatch} is injected with store.dispatch as a parameter.

I was stunned? It doesn't matter, let's summarize it in combination with practical use:

When we work with the library redux-thunk, when we use redux with components, we usually write this

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

Remember what createStore does when it receives a parameter with enhance in it?

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

In other words, it will become the following situation.

ApplyMiddleware (thunkMiddleware) (createStore) (reducer, initialState)

ApplyMiddleware (thunkMiddleware)

ApplyMiddleware takes thunkMiddleware as an argument and returns a function such as (createStore) = > (reducer, initialState, enhancer) = > {}.

ApplyMiddleware (thunkMiddleware) (createStore)

Call the function returned in the previous step (reducer, initialState, enhancer) = > {} with createStore as the parameter

ApplyMiddleware (thunkMiddleware) (createStore) (reducer, initialState)

Call with (reducer, initialState) as a parameter.

Inside this function, thunkMiddleware is called to monitor that type is the action of function

Therefore, if the action of the dispatch returns a function, which proves to be middleware, (dispatch, getState) is substituted as a parameter to proceed to the next step within the action. Otherwise, it is considered to be a normal action and will be further distributed through next (that is, dispatch).

In other words, applyMiddleware (thunkMiddleware), as an enhance, eventually plays the following role:

Check the action called by dispatch (for example, dispatch (addNewTodo (todo)). If action returns function after * calls, inject (dispatch, getState) as a parameter into the method returned by action, otherwise the action will be distributed normally, so our middleware will be completed.

Therefore, when the action needs to get the state internally, or to perform an asynchronous operation and distribute the event calls after the operation is completed, we can have the action return a function with the parameter (dispatch, getState) instead of the usual Object,enhance to detect it for correct processing.

BindActionCreator

This method feels rare, and I seldom use it personally.

In traditional writing, when we want to inject state and action into subcomponents, we usually do this:

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)

You can use bindActionCreators to convert action to an object with the same name key, but use dispatch to surround each action and call

The only scenario where you use bindActionCreators is when you need to upload action creator to a component, but you don't want the component to be aware of Redux, and you don't want to pass Redux store or dispatch to it.

/ applications within this 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; / / passed to the subcomponent let boundActionCreators = bindActionCreators (TodoActions, dispatch) Return ()} function mapStateToProps (state) {return {state}} export default connect (mapStateToProps) (TodoComponect)

BindActionCreator source code parsing

Function bindActionCreator (actionCreator, dispatch) {return (... args) = > dispatch (actionCreator (.. ARGs))} / / bindActionCreators expects an Object to be passed in as an actionCreators, where key: action export default function bindActionCreators (actionCreators, dispatch) {/ / if only an action is passed in The function if bound to dispatch (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?) is returned via bindActionCreator. 'null': typeof actionCreators}. `+ `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`)} / / traverse and bind to dispatch var keys = Object.keys (actionCreators) var boundActionCreators = {} for (var I = 0; I) via bindActionCreator distribution

< 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

After that, through the

Const finalMergeProps = mergeProps | | defaultMergeProps

Select the method of merging stateProps,dispatchProps,parentProps. The default merging method defaultMergeProps is:

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

Returns a function that takes Component as an argument. Inside this function, a Component called Connect is generated

/ /... Return function wrapWithConnect (WrappedComponent) {const connectDisplayName = `Connect (${getDisplayName (WrappedComponent)}) `/ / check parameter validity function checkStateShape (props, methodName) {} / / merge 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) {/ / call configureFinalMapState, using the passed mapStateToProps method (or default method) Enter state map into props} configureFinalMapState (store, props) {} computeDispatchProps (store, props) {/ / call configureFinalMapDispatch, using the incoming mapDispatchToProps method (or default method) Use dispatch to encapsulate action into props} configureFinalMapDispatch (store) Props) {} / / determine whether to update props updateStatePropsIfNeeded () {} updateDispatchPropsIfNeeded () {} updateMergedPropsIfNeeded () {} componentDidMount () {/ / Internal call this.store.subscribe (this.handleChange.bind (this)) this.trySubscribe ()} handleChange () {const storeState = this.store .getState () const prevStoreState = this.state.storeState / / A monitor for data Call this.setState ({storeState})} / / to cancel listening when sending changes Clear the cache 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)} / /.

We see that in the * of connect, Connect and WrappedComponent wrapped in hoistStatics are returned

What the heck is hoistStatics? Why use it?

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.

That is, it is similar to Object.assign in that it copies the static method in the child component into the parent component, but does not override the keyword method in the component (such as componentDidMount)

Import hoistNonReactStatic from 'hoist-non-react-statics'; hoistNonReactStatic (targetComponent, sourceComponent); is it helpful for you to read the above content? If you want to know more about the relevant knowledge or read more related articles, please follow the industry information channel, thank you for your support.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 280

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report