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