React Hooks 是目前社區很是火熱的一個新的特性,vue 3.0也引入了hooks,這個特性 在 React16.8
版本正式發佈。javascript
這篇文章不過多介紹hooks的基礎用法,相關的文章一大堆,我的很是推薦把精讀週刊裏關於hooks的文章所有看一遍。
前端精讀週刊前端
最近公司作了一個新項目,是後臺管理系統,咱們沒有引入redux,可是其實在某些比較複雜的頁面級模塊中,組件拆分的層級很是深,因此我想到了能夠利用React的Context
這個api進行跨層級的數據傳遞,利用useReducer
去作一個簡單的store來統一操做模塊的數據。vue
先貼一個利用Context配合useReducer的簡單示例java
定義Storereact
const CountContext = React.createContext();
const initialState = 0;
const reducer = (state, action) => {
switch (action.type) {
case 'increment': return state + 1;
case 'decrement': return state - 1;
case 'set': return action.count;
default: throw new Error('Unexpected action');
}
};
const CountProvider = ({ children }) => {
const contextValue = useReducer(reducer, initialState);
return (
<CountContext.Provider value={contextValue}> {children} </CountContext.Provider> ); }; const useCount = () => { const contextValue = useContext(CountContext); return contextValue; }; 複製代碼
組件中使用方法:git
const Counter = () => {
const [count, dispatch] = useCount();
return (
<div> {count} <button onClick={() => dispatch({ type: 'increment' })}>+1</button> <button onClick={() => dispatch({ type: 'decrement' })}>-1</button> <button onClick={() => dispatch({ type: 'set', count: 0 })}>reset</button> </div>
);
};
const Page = () => (
<> <CountProvider> <Counter /> <Counter /> </CountProvider> <CountProvider> <Counter /> <Counter /> </CountProvider> </> ); 複製代碼
很好,很方便,可是useReducer更適用於小型的模塊,咱們確定不會每一個模塊每次使用store都去寫這麼一段重複的Provider定義代碼,因此咱們要找出這個模式的痛點,而後進行一些封裝~github
createContext
-> 定義Provider
-> 找一個合適的地方把Provider放上去
這一系列流程。這些缺點是在項目開發中真實體驗到的,因此仍是有必要去作封裝的。redux
// store.js
import initStore from 'react-hook-store'
const store = {
// 初始狀態
initState: {
count: 0,
},
// 同步操做 必須返回state的拷貝值
mutations: {
// 淺拷貝state
add(payload, state) {
return Object.assign({}, state, { count: state.count + 1 })
},
},
// 異步操做,擁有dispatch的執行權
actions: {
async asyncAdd(payload, { dispatch, state, getters }) {
await wait(1000)
dispatch({ type: 'add' })
// 返回的值會被包裹的promise resolve
return true
},
},
// 計算屬性 根據state裏的值動態計算
// 在頁面中根據state值的變化而動態變化
getters: {
countPlusOne(state) {
return state.count + 1
},
},
}
export const { connect, useStore } = initStore(store)
複製代碼
// page.js
import React, { useMemo } from 'react'
import { Spin } from 'antd'
import { connect, useStore } from './store.js'
function Count() {
const { state, getters, dispatch } = useStore()
const { countPlusOne } = getters
const { loadingMap, count } = state
// loadingMap是內部提供的變量 會監聽異步action的起始和結束
// 便於頁面顯示loading狀態
// 須要傳入對應action的key值
// 數組內能夠寫多項同時監聽多個action
// 靈感來源於dva
const loading = loadingMap.any(['asyncAdd'])
// 同步的add
const add = () => dispatch({ type: 'add' })
// 異步的add
const asyncAdd = () => dispatch.action({ type: 'asyncAdd' })
return (
<Spin spinning={loading}> <span>count is {count}</span> <span>countPlusOne is {countPlusOne}</span> <button onClick={add}>add</button> <button onClick={asyncAdd}>async add</button> {/** 性能優化的作法 * */} {useMemo( () => ( <span>只有count變化會從新渲染 {count}</span> ), [count] )} </Spin>
)
}
// 必須用connect包裹 內部會保證Context的Provider在包裹Count的外層
export default connect(Count)
複製代碼
比較適用於單個比較複雜的小模塊,我的認爲這也是 react 官方推薦 useReducer 和 context 配合使用的場景。 因爲全部使用了 useContext 的組件都會在 state 發生變化的時候進行更新(context 的弊端),推薦渲染複雜場景的時候配合 useMemo 來作性能優化。api
這是一次簡單的封裝嘗試,雖然已經在生產環境跑起來了,可是覆蓋的場景仍是比較少,若是有優化的建議和吐槽都歡迎提出來~ 若是有小夥伴對實現的過程感興趣的話,也能夠留言,後續能夠增長源碼的相關解析。