封裝一個在react上更易用的redux框架
redux是一個用於管理狀態的前端框架,不僅可以用於react,還可以用於vue。它的原始碼和設計思想,我們在《顫抖吧!一起手寫一個redux框架!》已經講過,沒有看過的小夥伴們可以去看一下。
redux的核心是發明了store,通過dispatch來更改store裡的值。
可redux要想在react上使用,需要把兩者連線起來,這個時候react-redux就出現了。它的主要作用是:
- 在
componentDidMount
時store.subscribe
,這樣在store.dispatch
的時候就能回撥subscribe
的listener
。
具體看Provider.js中的原始碼實現:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { ReactReduxContext } from './Context'
class Provider extends Component {
//...
componentDidMount() {
this._isMounted = true
this.subscribe()
}
subscribe() {
const { store } = this.props
this.unsubscribe = store.subscribe(() => {
const newStoreState = store.getState()
if (!this._isMounted) {
return
}
this.setState(providerState => {
//小tip:return null代表不更新state
if (providerState.storeState === newStoreState) {
return null
}
return { storeState: newStoreState }
})
})
// ...
}
}
export default Provider
- 提供了context,可以實現跨元件通訊,而不用去狀態提升。
這其實用到了react自帶的api:context
。
具體實現是在Provider.js
中宣告context
,並把store.getState()
賦值給它。這樣其子元件中就可以拿到context
值,只不過要寫一些模板程式碼,這些程式碼都封裝在了connect.js
中。這也就是為什麼我們只要把我們的元件傳給高階元件connect
中就可以獲取到redux
的state
。 mapStateToProp
用來把store中的state傳給connect元件,所以在元件中可以通過this.props獲取store中state的值。mapDispatchToProps
用來把用dispatch包裹的actionCreator傳給元件(bindActionCreators中把dispatch給bind到actionCreator上),所以,在元件中通過props可以直接dispatch一個action。
以上大概就是要在react中使用redux要寫的程式碼。
其實,react-redux封裝的功能並不一定適合我們的業務場景,相反我們還多寫了很多與業務無關的模板程式碼!
因此,我們可以嘗試著自己去封裝一個更易用的redux框架。它有如下幾個特點:
- 可以成功地連線react和redux。
- 不需要在業務程式碼中手動bindActionCreator,也不需要寫mapDispatchToProps和mapStateToProps。
- 不用寫完reducer,再去寫action了。使用過react-redux的小夥伴們應該都發現了reducer和action實際上都是為了去更新資料層,卻要寫兩次非常類似的程式碼。
它的使用方法也很簡單,只需要讓你的頁面繼承這個basePage
就可以了。
import React from "react";
import { createStore, applyMiddleware } from "redux";
export default class BasePage {
constructor(props) {
super(props);
this.$actions = null;
this.$store = null;
this.state = {};
}
render() {
let { View } = this;
if (!View) return null;
let pageContext = {
page: this,
state: this.state
};
return <View state={this.state} page={this} />;
}
get store() {
if (this.$store) return this.$store;
this.$store = this.createStore();
return this.$store;
}
get actions() {
if (this.$actions) return this.$actions;
this.store; // trigger createStore
this.$actions = {};
if (!util.isObject(this.model)) return this.$actions;
Object.keys(this.model).forEach(type => {
this.$actions[type] = payload => this.store.dispatch({ type, payload });
});
return this.$actions;
}
createReducer() {
return (state, action) => {
let model = this.model;
let nextState = state;
if (util.isObject(model)) {
let handler = reducer[action.type];
if (util.isFunction(handler)) {
nextState = handler(nextState, action.payload);
}
} else if (util.isFunction(model)) {
nextState = reducer(nextState, action);
}
return nextState;
};
}
createStore() {
const middlewares = [];
if (process.env.NODE_ENV === "development") {
const reduxLogger = require("redux-logger");
const logger = reduxLogger.createLogger();
middlewares.push(logger);
}
const createStoreWithMiddleware = applyMiddleware(...middlewares)(
createStore
);
let store = createStoreWithMiddleware(this.createReducer(), this.state);
store.unsubscribe = store.subscribe(() => {
let nextState = store.getState();
this.setState(nextState);
});
Object.defineProperty(store, "actions", {
get: () => this.actions
});
return store;
}
可以看到,如上框架程式碼行數並不多,下面我來為大家來講解一下。
首先,先看render()
,在render
中給每個view
傳入了page
:
<View state={this.state} page={this} />
這主要是用來在元件中呼叫action來更新state。
例如,我們在CounterView
中呼叫INCRE_BY
。
export default function CounterView({ state, page }) {
const { count } = state;
return (
<View>
<TouchableOpacity onPress={()=> {
page.actions.INCRE_BY
}}>
<Text style={{ backgroundColor: "red" }}>increase</Text>
</TouchableOpacity>
</View>
);
}
此時頁面才開始createStore
。。或者說,只有呼叫頁面第一個action來開始createStore
。因為,在呼叫page.actions.INCRE_BY
時,實際上會先呼叫
get actions() {
if (this.$actions) return this.$actions;
this.store; // trigger createStore
this.$actions = {};
if (!util.isObject(this.reducer)) return this.$actions;
Object.keys(this.reducer).forEach(type => {
this.$actions[type] = payload => this.store.dispatch({ type, payload });
});
return this.$actions;
}
此時,this.$actions
實際上是空的,所以這個時候就會用this.model
來構建action
。這裡的model
不光可以構建action
,還可以構建出reducer
!
我們來看下mode.js是怎樣的:
export const INCRE = state => {
let count = state.count + 1;
return {
...state,
count
};
};
export const DECRE = state => {
let count = state.count - 1;
return {
...state,
count
};
};
export const INCRE_BY = (state, aaa = 2) => {
let count = state.count + aaa;
return {
...state,
count
};
};
看到這個model
我相信你大致就能猜到我是如何通過model
來構建reducer
和action
的了——構建action
時,取出model
的key
作為action
名,並把每個function
用dispatch
包裹一下;構建reducer
時,就直接呼叫model
中相應key
的function
,注意這裡每一個function
都return
一個新的state
。
而上面在呼叫action
時,當發現store
為空時,會接著去createStore
。
createStore() {
const middlewares = [];
if (process.env.NODE_ENV === "development") {
const reduxLogger = require("redux-logger");
const logger = reduxLogger.createLogger();
middlewares.push(logger);
}
const createStoreWithMiddleware = applyMiddleware(...middlewares)(
createStore
);
let store = createStoreWithMiddleware(this.createReducer(), this.state);
store.unsubscribe = store.subscribe(() => {
let nextState = store.getState();
this.setState(nextState);
});
Object.defineProperty(store, "actions", {
get: () => this.actions
});
return store;
}
以上我們就完成了一個比react-redux更輕量級的redux框架,該框架使得我們能儘可能少地在業務程式碼中寫無腦的模板程式碼。
最後,該框架是參考自@工業聚,他對於技術的態度,給了我很多前進的動力!
相關文章
- 【JavaScript框架封裝】實現一個類似於JQuery的動畫框架的封裝JavaScript框架封裝jQuery動畫
- 如何基於 React 封裝一個元件React封裝元件
- 【JavaScript框架封裝】實現一個類似於JQuery的CSS樣式框架的封裝JavaScript框架封裝jQueryCSS
- 封裝redux中的createStore封裝Redux
- 【JavaScript框架封裝】公共框架的封裝JavaScript框架封裝
- 封裝react antd的upload上傳元件封裝React元件
- 基於react + redux的Bone框架ReactRedux框架
- 手挽手帶你學React:四檔(上)一步一步學會react-redux (自己寫個Redux)ReactRedux
- 從零開始學React:四檔(上)一步一步學會react-redux (自己寫個Redux)ReactRedux
- 封裝一個簡易的上傳附件方法封裝
- 在 React 中使用 ReduxReactRedux
- 封裝React Hook函式useState實現更優雅的setValue封裝ReactHook函式
- 【React系列】動手實現一個react-reduxReactRedux
- React 知識梳理(三):手寫一個自己的 React-reduxReactRedux
- Redux在React中的使用小結ReduxReact
- react封裝一個可自定義內容的modal彈框元件React封裝元件
- React + Redux + Redux-thunk + Semantic-UI-React 實現一個簡單天氣AppReactReduxUIAPP
- 在RN專案上對axios的封裝iOS封裝
- 在 vue-cil 裡封裝一個簡單的 axiosVue封裝iOS
- 封裝一個通用的PopupWindow封裝
- react 知識梳理(二):手寫一個自己的 reduxReactRedux
- 對於封裝react元件的一些思考封裝React元件
- 封裝框架的實踐封裝框架
- React — 通用hooks封裝ReactHook封裝
- react、redux、react-redux之間的關係ReactRedux
- react+react-router+redux+Node.js+socket.io寫一個聊天webappReactReduxNode.jsWebAPP
- 一個簡單的 Amqp 封裝MQ封裝
- 封裝一個自己的js庫封裝JS
- 【like-react】手寫一個類似 react 的框架React框架
- 在Flutter中封裝redux的使用Flutter封裝Redux
- 優雅的在React專案中使用ReduxReactRedux
- 封裝react antd的表格table元件封裝React元件
- Vue封裝一個簡單輕量的上傳檔案元件Vue封裝元件
- React 入門-redux 和 react-reduxReactRedux
- 實現一個屬於自己的React框架(一)React框架
- gorpc: 一個簡單,易用,高效能,可插拔的微服務框架GoRPC微服務框架
- React,Redux,React-redux的錯綜複雜關係ReactRedux
- 小推理:React18比老版React更優秀的一個地方React