本文源代碼地址html
本文Github地址,歡迎starreact
由於redux是基於flux模式的,因此若是你想對flux有所瞭解,能夠查看個人這篇文章Vuex和Redux都使用的Flux設計模式精簡版實現git
先來看一下,完成文章標題所說的,須要完成哪些任務:(看到下面這個list,先不要被嚇跑,由於每一個任務平均實現代碼都沒有25行,只是爲了分的細一點)github
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.npm
接下來,就照着項目提交的commit一個一個的講:redux
(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;
}
}
複製代碼
調試成功的結果以下:api
(3)commit3-commit4:redux中action的實現app
這兩個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 的理解以及應用)