作者:殷榮檜@騰訊
因為redux是基於flux模式的,所以如果你想對flux有所瞭解,可以檢視我的這篇文章Vuex和Redux都使用的Flux設計模式精簡版實現
先來看一下,完成文章標題所說的,需要完成哪些任務:(看到下面這個list,先不要被嚇跑,因為每個任務平均實現程式碼都沒有25行,只是為了分的細一點)
TODO LIST(計劃列表)
1.redux中reducer的實現
2.redux中action的實現
3.redux中store的實現
3.5 先不使用redux,直接用react實現一個主題定製頁面的開發
  3.5.1 使用props傳參完成父子元件共享同一個全域性變數(演示全域性變數的原始層層傳遞共享資料)
  3.5.2 使用react提供的Context上下文完成父子元件全域性變數的共享(演示全域性變數使用react context 跨越元件共享資料)
4.結合自己實現的redux實現一個主題定製頁面的開發
5.redux中的connect的實現。(結合react中的context實現)
6.結合connect react把主題定製頁面再實現一遍
7.redux中新增mapStateToProps
8.redux中新增mapDispatchToProps
9.結合mapStateToProps,mapDispatchToProps 主題定製頁面
10.redux中的provider的實現
11.使用provider重構程式碼
12.使用真正的react-redux替代進來,看專案是否正常執行。程式碼整理,provider中頁面重新整理相關的移動到connect,語法檢查,錯誤檢查,新增註釋,合併commit
複製程式碼
接下來我們試著一個一個去實現。當然這其中包括了為什麼需要redux的歷史演化的過程,總的用了十幾個commit來完成了這篇文章,基本上每個commit實現TODO list中的一個計劃(在這牆裂推薦你使用source tree這個工具來檢視各個commit都幹了啥,修改了啥,對閱讀原始碼很有幫助)。在完成一個完整的功能後都會使用完整的demo進行演示,基本上可以覆蓋redux的進化歷程了。最後真正實現redux的部分連100行程式碼都不到,所以你千萬不要被redux的名頭嚇跑。最後再不要臉一下,如果你覺得文章寫得還行,歡迎您在我的Github中送上star.
接下來,就照著專案提交的commit一個一個的講:
(1)commit1:完成TODO list規劃 這個commit主要是完成了實現這篇文章所要完成的任務。也就是上面陳列的TODO list.
(2)commit2:完成reducer模組的功能 沒有使用任何的構建工具,直接開啟index.html就可以執行。需要在Google 除錯工具的控制檯中檢視結果,介面上並無內容。這個commit主要就是實現了一個reducer,並在index.html中呼叫了reducer.js,主要是除錯了一下reducer是否可以正常的執行。reducer實現的程式碼部分如下。
var reducer = (state = initialState, action) => {
switch(action.type) {
case CREATE_NOTE: {
let currentId = state.nextNoteId;
return {
nextNoteId: currentId + 1,
notes: {
...state.notes,
[currentId]: action.content
},
};
}
case UPDATE_NOTE: {
let {id, content} = action;
return {
...state,
notes: {
...state.notes,
[id]: content
}
};
}
default:
return state;
}
}
複製程式碼
除錯成功的結果如下:
(3)commit3-commit4:redux中action的實現
這兩個commit主要實現了redux中action的定義。同樣在index.html中引入了actions.js來對所寫的action進行測試。
const CREATE_NOTE = "CREATE_NOTE";
const UPDATE_NOTE = "UPDATE_NOTE";
// 新增一條備忘錄
let create_action = {
type: CREATE_NOTE,
content: '明天下午要開會'
};
// 更新一條備忘錄
let update_action = {
type: UPDATE_NOTE,
id: 1,
content: '好像記錯了,是明天上午要開會'
};
複製程式碼
下圖為測試成功控制檯的結果。
(4)commit5-commit6:完成store的實現,基本實現redux功能
function createStore(reducer) {
let state = undefined;
const subscribers = [];
let store = {
getState: () => state,
dispatch: (action) => {
state = reducer(state, action);
subscribers.forEach(handler => handler());
},
subscribe: (handler) => {
// handler就要比如備忘錄有更新就傳送通知到對應的人之類的
subscribers.push(handler);
return () => {
let index = subscribers.indexOf(handler);
if (index >= 0) {
// 防止搜尋不到index為-1時,把subscribers最後一個刪除了
subscribers.splice(0, index);
}
}
}
}
store.dispatch(init_action); // 初始化一下備忘錄
return store;
}
複製程式碼
基本實現redux後,來測試一下自己寫的redux是否正常執行。以下為正常執行的結果:
(5)commit7:使用props傳參完成父子元件通訊,完成主題切換 使用react的專案初始化工具,詳情參考react專案初始化工具,完成專案的初始化工作,並使用最原始的方式進行父子元件的傳參,使用了props進行傳參。使用這種方式的弊端就是,如果要從父向兒子,孫子,重孫....一直傳到祖宗十九代的時候,每個子元件都要寫向下傳遞的程式碼,非常的冗餘和難以維護。在專案中才使用了幾層程式碼都能感覺到程式碼的冗餘和難以維護。(注意:專案中的Index.js需要修改為index.js才可以在my-app目錄下使用npm run start執行,我的鍋,不要意思,之後的commit都要改成index.js才可以正常執行)
class Content extends Component {
render() {
return (
<div>
<span
color={this.props.color}
style={{color: this.props.color}}>
主內容區域
</span>
<br/>
<ChildContent
switchColor={this.props.switchColor}
color={this.props.color}
></ChildContent>
</div>
);
}
}
複製程式碼
以下是完成的主題切換的效果:
(6)commit8:完成react提供的Context上下文完成父子元件全域性變數的共享 為了避免程式碼的冗餘。可以考慮使用react中提供的全域性context來進行完成。這樣就可以避免層層使用props來進行傳參。在任何一行使用contextTypes就可以完成引用全域性變數。但是這樣寫程式碼的弊端依然很明顯。程式碼中依然存在冗餘。但維護性相對於使用props較好。以下是使用contextTypes在元件中引用屬性的方法。
class Title extends Component {
static contextTypes = {
color: PropTypes.string
}
render() {
return (
<div style={{color: this.context.color}}>我是文章的標題</div>
);
}
}
複製程式碼
(7)commit9-commit10:結合自己實現的redux實現一個主題定製頁面的開發
到這就可以結合自己開發的redux實現一個簡單的主題定製頁面的開發。主要在紅色主題與藍色主題之間切換。來測試自己所寫的redux是否可以正常工作。主要實現的結果和上面相同。通過在my-app/的目錄下執行npm run start就可成功啟動頁面。
(8)commit11-commit13:redux中connect的實現
connect實現部分的程式碼如下:
let connect = (WrappedComponent) => {
class connect extends Component {
static contextTypes = {
store: PropTypes.object,
}
componentWillMount() {
this.store = this.context.store;
}
render() {
return <WrappedComponent store = {this.store}></WrappedComponent>
}
}
return connect;
}
複製程式碼
(9)commit14:redux中mapStateToProps的實現
mapStateToProps實現的程式碼如下:
let mapStateToProps = (state) => {
return {
themeColor: state.color,
}
}
複製程式碼
(10)commit15:redux中mapDispatchToProps的實現並結合自己實現的mapStateToProps和mapDispatchToProps更新主題修改小應用
let mapDispatchToProps = (dispatch) => {
return {
changeThemeColor: function(color) {
dispatch({type: UPDATE_THEME, color})
}
}
}
複製程式碼
(11)commit16:redux中provider的實現,並使用其更新主題修改小程式
class Provider extends Component {
static propTypes = {
store: PropTypes.object,
}
static childContextTypes = { // 定義父子元件共享的變數
store: PropTypes.object,
}
getChildContext () {
return {
store: this.props.store
}
}
componentWillMount() {
this.props.store.subscribe(() => this.updateComponent());
}
updateComponent() {
// 每次store資料更新後重新渲染一下頁面
this.setState({color: this.props.store.getState().color});
}
render() {
return this.props.children;
}
}
複製程式碼
(12)commit17-commit18:完成redux的簡單仿寫,並將專案中引用自己所寫redux部分全部改成react中的redux,好看程式碼是否可行。
-import createStore from './redux/store';
+import {createStore} from 'redux';
-import Provider from './redux/provider';
+import {Provider} from 'react-redux';
複製程式碼
通過上述的替換之後,切換主題的小應用工作正常(需要使用命令npm run dev,手賤,把start改成了dev),如下圖所示。可見所仿寫的redux基本成功,當然其中精細部分與原版有所差別,但是整體的原理上基本相似。所以到這基本上就瞭解了redux的工作原理,以後再也不要對redux的語法死記硬背了,因為你都已經寫過一遍。
部門正在招新,為騰訊企業產品部,隸屬CSGI事業群。福利不少,薪水很高,就等你來。有興趣請猛戳下方兩個連結。 www.lagou.com/jobs/521039… www.zhipin.com/job_detail/…
(如果發現文章中程式碼存在問題,請批評指正並留言,我會更新到文章以及程式碼中去,謝謝)
參考資料
code-cartoons.com/a-cartoon-i… cartoon intro to redux) zapier.com/engineering…(build yourself a redux) www.sohamkamani.com/blog/2017/0… (React-redux "connect" explained) codesandbox.io/s/github/re…(provider and connect example) huziketang.mangojuice.top/books/react… (React.js 的 context) huziketang.mangojuice.top/books/react… (動手實現 Redux(四):共享結構的物件提高效能,為什麼需要使用...(spread operator)來提供效能) juejin.im/post/5a90e0…(聊一聊我對 React Context 的理解以及應用)