• createStore.js
    • About the initial state value
  • combinReducer.js
    • A Reducer corresponds to an attribute in the state that corresponds to a component
  • Dispatch [d ɪ ‘sp æ t ʃ]
    • bindActionCreators.js
  • react-redux
    • Provider.js
    • connect.js

pre-notify

emmm… This is an article that focuses on the implementation of the source code, followed by the use of considerations, please read carefully.

Let’s start with a widely circulated overview:

createStore.js

//createStore.js

// getState: Get the state stored in the repository
// dispatch: Perform an action to update state(even if state has not changed)
// SUBSCRIBE: the cb to be executed after the state is registered

export default function createStore(reducer,preloadedState){
	let state = preloadedState;
    let listeners = [];
    
    dispatch({}); //createStore is called manually once, assigning an initial value to state
    
    function dispatch(action){
    	state = reducer(state,action);
        listeners.forEach(cb= >cb());
    }
    
    function getState(){
    	return JSON.parse(JSON.stringify(state));
    }
    
    function subscribe(listener){
    	listeners.push(listener);
        return function(){
        	listeners = listeners.filter(item= >item!=listener);
        }
    }
    
    return {getState,dispatch,subscribe}
}
Copy the code

About the initial state value

The initial value of state is commented in the corresponding code sample section above.

Note also that when we call createStore() to initialize a repository, we can pass in a preloadedState parameter as the second parameter to createStore, which also initializes the state of the repository.

export default function createStore(reducer,preloadedState){
    letstate = preloadedState; .Copy the code

If this were done, the respective initState we wrote in each Reducer would no longer be effective.

Reducer of a componentlet initState = {
  number:0
};

export default function reducer(state = initState,action){
  switch(action.type){
  ...
Copy the code

So, we can either pass in an object (preloadedState) when createStore to initialize the state of all components in a unified manner, or choose to initialize their respective initstates in the reducer corresponding to each component

combinReducer.js

// combinReducers.js

function combineReducers(reducers){
	return function(state = {},action){ //state={} {} is given only for compatibility with state[attr]
    	let newState = {};
        for(let attr in reducers){
    	    let reducer = reducers[attr];
            newState[attr] = reducer(state[attr],action); State [attr] may be undefined. We usually assign an initial value of initState to each reducer
        }
        returnnewState; }}// --- --- ---

// The following is an advanced version
export default reducers=>(state={},action) = >Object.keys(reducers).reduce((currentState,key) = >{
  currentState[key] = reducers[key](state[key],action);
  returncurrentState; }, {});Copy the code

A Reducer corresponds to an attribute in the state that corresponds to a component

Generally, each reducer is put in a folder named reducers, and the index.js file in this folder is exported uniformly.

//reducers/index.js

import counter from './counter';
import counter2 from './counter2';
import {combineReducers} from '.. /.. /redux'// The merge should return a new functionexport default combineReducers({
  counter
  ,counter2
});
Copy the code

This is what happens when dispatch is called to change the original state

So, if A component in the same repository triggers an action (for example, A) while another component (for example, B) does not, both reducer operations will be performed, but the actions in reducer cannot have the same name (A and B), So, Component B cannot find the actions in A in its reducer. After A trip, it will go out without any impact on the original state.

Counter :{number:0} counter2:{number:0} counter2:{number:0}Copy the code

We can get it in the component like this

//store/index.js

import {createStore} from '.. /redux';
import reducer from './reducers';

let store = createStore(reducer); // {getState,dispatch,subscribe}
export default store;

// --- --- ---

// In a component
import store from '.. /store'; . this.state = {number:store.getState().counter2.number}; .Copy the code

Dispatch [d ɪ ‘sp æ t ʃ]

Dispatch, which means dispatch, is a method of generating output on createSotre.

Yeah, give it away. Give what away? Dispatches are designed to dispatch actions/actions, each of which changes some property on the corresponding property of a component on state.

The original action looks like this

. <button onClick={()=>store.dispatch({type:types.INCREMENT})}>+</button>
...
Copy the code

But we typically create an Actions file in a project, and then create a module for each component. This module holds all the actions for that component

// store/actions/counter.js

import * as types from '.. /action-types'; //actionCreator creates the action functionexport default {
  increment() {return {type:types.INCREMENT}
  }
  ,decrement() {return {type:types.DECREMENT}
  }
}
Copy the code

And that’s what happens when it’s distributed

. import actions from'.. /store/actions/counter'; . <button onClick={()=>store.dispatch(actions.increment())}>+</button> ...Copy the code

bindActionCreators.js

emmm… Some people find the above distribution still hard to write, hence this module (don’t ask me what I think about these products).

//bindActionCreators.js

export default function bindActionCreators(actions,dispatch){
  let newActions = {};
  for(let attr in actions){
    newActions[attr] = function(){
      // actions[attr] => increment() {return {type:types.INCREMENT}} dispatch(actions[attr].apply(null,arguments)); }}return newActions;
}
Copy the code

And so we ended up writing it like this

. import actionsfrom '.. /store/actions/counter'; . let newActions = bindActionCreators(actions,store.dispatch); . <button onClick={newActions.increment}>+</button>.Copy the code

react-redux

When we used Redux in React, a lot of the code was redundant

Such as

.componentDidMount(){
    this.unsubscribe = store.subscribe(()=>{
      this.setState({number:store.getState().counter.number});
    });
}
componentWillUnmount(){ this.unsubscribe(); }...Copy the code

Again for instance

constructor(){
    super();
    this.state = {number:store.getState().counter2.number};
  }
Copy the code

Again or

import {bindActionCreators} from '.. /redux'
let newActions = bindActionCreators(actions,store.dispatch);
Copy the code

So, what react-Redux does is it takes all this redundant code and separates it into a template, um, a higher-order component.

Provider.js

The main purpose of this component is to pass stores to descendant components

import React,{Component}from 'react';
import propTypes from 'prop-types';

export default class Provider extends Component{
  static childContextTypes = {
    store:propTypes.object.isRequired
  };
  getChildContext() {return {store:this.props.store};
  }
  render() {returnthis.props.children; }}Copy the code

This is how we use it

. import store from'./redux2/store'. <Provider store={store}> <React.Fragment> <Component1/> <Component2/> </React.Fragment> </Provider> ...Copy the code

connect.js

First we normally call the higher-order component in the component like this

/ / Counter. Js componentexport default connect(
  state=>state.counter
  ,actions
)(Counter);
Copy the code

The first parameter is to filter other data in the warehouse that is not the component’s data.

The second argument, Actions, is the component’s own action and can be passed in two alternative forms:

  • Form of object
//actionCreator [object], which creates the action function //action file for each component in the previous actions folder {increment() {return {type:types.INCREMENT}
  }
  ,decrement() {return {type:types.DECREMENT}
  }
}
Copy the code
  • Form of function
// That is the previous passbindActionCreators processed the subsequent Actionslet mapDispatchToProps = dispatch => ({
  increment:()=>dispatch({type:types.INCREMENT})
  ,...
});
Copy the code

In higher-order components, the form of the object is converted to what the second function form looks like after execution.

//connect.js

export default function(mapStateToProps,mapDispatchToProps){
	return function(WrappedComponent){
    	class ProxyComponent extends Component{
        	static contextTypes = {
            	store:propTypes.object
            }
            constructor(props,context){
            	super(props,context);
                this.store = context.store;
                this.state = mapStateToProps(this.store.getState());
            }
            componentDidMount(){
            	this.unsubscribe = this.store.subscribe(()=>{
                	this.setState(mapStateToProps(this.store.getState()));
                });
            }
            componentWillUnmount(){
            	this.unsubscribe();
            }
            render() {let actions = {};
                if(typeof mapDispatchToProps === 'function'){
                	actions = mapDispatchToProps(this.store.dispatch);
                }else if(typeof mapDispatchToProps === 'object'){
                	actions = bindActionCreators(mapDispatchToProps,this.store.dispatch);
                }
                return<WrappedComponent {... this.state} {... actions}> } }returnProxyComponent; }}Copy the code

After high-level component wrapping, each component only has its own part of the data in the warehouse, and the actions of each component are distributed to the corresponding components as props.

Note: mapStateToProps is state=>state in chestnut. It is also possible that we only have one component and do not use combinReducers, which means that the data structure in our state will only have one layer, that is, all the attributes under this one component, So, In this case our mapStateToProps function would look like this: state=>state or state=>{… state}


Reference:

  • redux.js.org/
  • www.redux.org.cn/

=== ToBeContinue ===