- createStore.js
- 關于state初始值
- combinReducer.js
- 一個reducer對應state里的一個屬性對應一個組件
- dispatch [d?'sp?t?]
- bindActionCreators.js
- react-redux
- Provider.js
- connect.js
[TOC]
## pre-notify
emmm...這是一篇偏重于源碼實現的文章,其次是使用的注意事項,閱讀請謹慎。
先上一張廣為流傳的總覽圖:

## createStore.js
```javascript
//createStore.js
// getState:獲取倉庫里存儲的state
// dispatch:執行動作,更新state(即使state沒有變化)
// subscribe:注冊更新完state后要執行的cb
export default function createStore(reducer,preloadedState){
let state = preloadedState;
let listeners = [];
dispatch({}); //createStore時手動調用一次,給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}
}
```
### 關于state初始值
關于state初始值的,在上面相應的代碼示例部分已經做出了相應的注釋。
另外需要注意的是當我們調用`createStore()`初始化一個倉庫時,可以傳入一個`preloadedState`參數作為createStore的第二個參數傳入,它也能讓倉庫的`state`初始化。
```
export default function createStore(reducer,preloadedState){
let state = preloadedState;
...
```
假若像上面這樣初始化了,那么我們在每個reducer中寫的各自的`initState`就不再有效果。
```
// 某個組件的reducer
let initState = {
number:0
};
export default function reducer(state = initState,action){
switch(action.type){
...
```
So,我們即可以在`createStore`時候傳入一個對象(`preloadedState`)統一初始化所有組件的state,也可以選擇在各自組件對應的`reducer`中初始化各自的`initState`
## combinReducer.js
```javascript
// combinReducers.js
function combineReducers(reducers){
return function(state = {},action){ //state={}這里賦了{},只是為了兼容state[attr]不報錯
let newState = {};
for(let attr in reducers){
let reducer = reducers[attr];
newState[attr] = reducer(state[attr],action); //state[attr]可能為undefined,我們一般會在每個reducer里賦一個initState的初始值
}
return newState;
}
}
// --- --- ---
//以下為高逼格版
export default reducers=>(state={},action)=>Object.keys(reducers).reduce((currentState,key)=>{
currentState[key] = reducers[key](state[key],action);
return currentState;
},{});
```
### 一個reducer對應state里的一個屬性對應一個組件
一般將各個reducer放在一個名為`reducers`的文件夾下,并且又該文件夾下的index.js文件統一導出。
```
//reducers/index.js
import counter from './counter';
import counter2 from './counter2';
import {combineReducers} from '../../redux'
// 合并后應該返回一個新的函數
export default combineReducers({
counter
,counter2
});
```
當調用dispatch時是這么運轉從而改變原state的

So,如果同一個倉庫里的一個組件觸發了動作(比如說A),而另一個組件(比如說B)沒有觸發,雖然都會執行一次各自的reducer,但由于reducer里的動作是不能重名的(A組件和B組件),So,B組件在自己的reducer中是找不到A里的動作的,逛了一圈就會出去,不會對原狀態有任何影響。
```
// createStore里的state長這樣
{
counter:{number:0} //組件1的狀態數據
,counter2:{number:0} //組件2的狀態數據
}
```
我們能在組件里這樣拿到
```javascript
//store/index.js
import {createStore} from '../redux';
import reducer from './reducers';
let store = createStore(reducer); // {getState,dispatch,subscribe}
export default store;
// --- --- ---
// 一個組件中
import store from '../store';
...
this.state = {number:store.getState().counter2.number};
...
```
## dispatch [d?'sp?t?]
dispatch,派發的意思,它是createSotre產出的一個方法。
嗯,派發,派發什么呢?dispatch是專門用來派發`action/動作`的,每個action都會改變state上**某個組件對應的屬性**上的某**些**屬性。
最原始的派發任務(action)是這樣的
```
...
<button onClick={()=>store.dispatch({type:types.INCREMENT})}>+</button>
...
```
但我們在項目中一般會創建一個`actions`文件件,然后在里面按照不同的組件來創建一個模塊,這個模塊存放著這個組件所有的`action`
```
// store/actions/counter.js
import * as types from '../action-types';
//actionCreator 創建action的函數
export default {
increment(){
return {type:types.INCREMENT}
}
,decrement(){
return {type:types.DECREMENT}
}
}
```
于是乎,派發時就變成這樣
```
...
import actions from '../store/actions/counter';
...
<button onClick={()=>store.dispatch(actions.increment())}>+</button>
...
```
### bindActionCreators.js
emmm...有些人覺得上面的派發寫起來仍然很費勁,于是乎就寫了這么個模塊(別問我這些貨是怎么想的)
```
//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;
}
```
于是乎我們最終派發寫起來是這樣的
```javascript
...
import actions from '../store/actions/counter';
...
let newActions = bindActionCreators(actions,store.dispatch);
...
<button onClick={newActions.increment}>+</button>
...
```
## react-redux
我們在react使用redux時,其實有許多代碼是冗余的
比如
```
...
componentDidMount(){
this.unsubscribe = store.subscribe(()=>{
this.setState({number:store.getState().counter.number});
});
}
componentWillUnmount(){
this.unsubscribe();
}
...
```
再比如
```
constructor(){
super();
this.state = {number:store.getState().counter2.number};
}
```
又或則
```
import {bindActionCreators} from '../redux'
let newActions = bindActionCreators(actions,store.dispatch);
```
So,`react-redux`的作用就是把這些冗余的代碼抽離成一個模板出來,嗯,弄一個高階組件。
### Provider.js
這個組件主要是以便將`store`傳遞給子孫組件
```
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(){
return this.props.children;
}
}
```
我們一般是這樣使用的
```
...
import store from './redux2/store'
...
<Provider store={store}>
<React.Fragment>
<Component1/>
<Component2/>
</React.Fragment>
</Provider>
...
```
### connect.js
首先我們一般在組件中這樣調用這個高階組件
```
//Counter.js 組件中
export default connect(
state=>state.counter
,actions
)(Counter);
```
其中第一個參數是為了過濾倉庫中非該組件數據的其它數據。
第二個參數actions是組件自己的動作,有兩種可選的傳遞形式:
- 對象的形式
```
//actionCreator【對象】,用來創建action函數
//即之前的actions文件夾下的每個組件的action文件
{
increment(){
return {type:types.INCREMENT}
}
,decrement(){
return {type:types.DECREMENT}
}
}
```
- 函數的形式
```
//即之前經過bindActionCreators 處理過后的 actions
let mapDispatchToProps = dispatch => ({
increment:()=>dispatch({type:types.INCREMENT})
,...
});
```
其中對象的形式在高階組件中會被轉換為第二種函數形式執行后的樣子(通過`bindActionCreators`模塊)。
```
//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}>
}
}
return ProxyComponent;
}
}
```
經過高階組件包裝后,每個組件都**只會**擁有倉庫中屬于自己的那部分數據,并且屬于每個組件的動作還會**作為props**分發給對應的組件。

>**注意:** `mapStateToProps`在上栗中是`state=>state.某組件對應的state屬性名`的形式,但也有可能我們只有一個組件,沒有使用`combinReducers`,這意味著我們的state中的數據結構只會有一層,即這個一個組件下的所有屬性,So,這樣的情況下我們的mapStateToProps函數應該是這樣的state=>state or state=>{...state}
---
參考:
- [https://redux.js.org/](https://redux.js.org/)
- [http://www.redux.org.cn/](http://www.redux.org.cn/)
=== ToBeContinue ===
- 空白目錄
- 01.JSX,了解一下?
- JSX與虛擬DOM
- React
- 02.React文檔精讀(上)`
- React路由
- 關于BrowserRouter
- 關于Route
- 應用
- 權限認證
- case1
- context
- 新context
- 03.React路由
- 04.Diff
- 05.styled-components
- redux設計思想與API
- redux實現1
- 06.redux2
- 06.redux3
- 關于狀態初始化
- saga
- 新版
- 使用saga進行業務邏輯開發
- react-router-redux
- React性能優化
- immutable使用
- 未整理
- FAQ
- 常用中間件
- pureComponent
- 項目相關總結
- antd分尸
- 按需加載
- ReactWithoutJSX
- 我的組件庫
- C領域
- 用戶接口
- htmlType
- style
- show
- conjure
- grid
- inject
- stop
- 內部接口
- 衍生組件
- Button
- 報錯集錦
- ReactAPI
- 類上的那些屬性
- prop-types
- React.createElement
- React.cloneElement
- React.Children和props.children
- react元素和react組件關于作為children方面的那些問題
- react組件與虛擬dom
- ref