針對 react hooks 的新版本解決方案
一.redux維持原方案
若想要無縫使用原來的 redux,和其配套的中間件 promise,thunk,saga 等等的話
可以使用 redux-react-hook
github 鏈接 redux-react-hook
一個簡單的使用例子:
import {useDispatch, useMappedState} from 'redux-react-hook';
export function DeleteButton({index}) {
// 類似於以前 react-redux 中的 connect 函數
const mapState = useCallback(
state => ({
canDelete: state.todos[index].canDelete,
name: state.todos[index].name,
}),
[index],
);
// 獲取 redux 的數據
const {canDelete, name} = useMappedState(mapState);
// 獲取 dispatch
const dispatch = useDispatch();
// button click handle
const deleteTodo = useCallback(
() =>
dispatch({
type: 'delete todo',
index,
}),
[index],
);
return (
<button disabled={!canDelete} onClick={deleteTodo}>
Delete {name}
</button>
);
}
使用方法和以前一致
二.使用 useReducer 與 context
在 index 或 app 中提供全局的 redux 與 dispatch
function isPromise(obj) {
return (
!!obj &&
(typeof obj === "object" || typeof obj === "function") &&
typeof obj.then === "function"
);
}
function wrapperDispatch(dispatch) {
// 功能和 redux-promise 相同
return function (action) {
isPromise(action.payload) ?
action.payload.then(v => {
dispatch({type: action.type, payload: v})
}).catch((error) => {
dispatch(Object.assign({}, action, {
payload: error,
error: true
}));
return Promise.reject(error);
})
:
dispatch(action);
};
}
function Wrap(props) {
// 確保在 dispatch 后不會刷新APP組件
const [state, dispatch] = useReducer(reducers, ReducersValue);
console.log('render wrap')
return (<MainContext.Provider value={{state: state, dispatch: wrapperDispatch(dispatch)}}>{props.children}</MainContext.Provider>)
}
function App() {
console.log('render App')
return <Wrap>
<Router>
<Switch>
<Route path="/login" component={Login} exact/>
<Route path="/" component={MainIndex}/>
</Switch>
</Router>
</Wrap>
}
具體使用:
function useDispatch() {
// 獲取 dispatch
const store = useContext(MainContext);
return store.dispatch;
}
function useStoreState(mapState) {
//存儲 state 且判斷是否需要 render
const {state:store} = useContext(MainContext);
const mapStateFn = () => mapState(store);
const [mappedState, setMappedState] = useState(() => mapStateFn());
const lastRenderedMappedState = useRef();
// Set the last mapped state after rendering.
useEffect(() => {
lastRenderedMappedState.current = mappedState;
});
useEffect(
() => {
console.log('useEffect ')
const checkForUpdates = () => {
const newMappedState = mapStateFn();
if (!_.isEqual(newMappedState, lastRenderedMappedState.current)) {
setMappedState(newMappedState);
}
};
checkForUpdates();
},
[store, mapState],
);
return mappedState
}
// 組件內使用
const ResourceReducer = useStoreState(state => state.ResourceReducer)
const dispatch = useDispatch()
他的功能已經足夠了,在使用的地方使用函數即可,很方便
但是也有一些不足的地方是在根源上的,即 context,
在同一個頁面中 如果有多個使用 context 的地方
那么如果一旦dispatch ,其他的所有地方也會觸發render 造成資源的浪費,小項目還好,大項目仍舊不可
取
(除非 react 的 context 函數添加 deps)
三.自定義解決方案
原理就是存儲一個全局變量 ,通過 import 引入;
我自己寫了一個例子:https://github.com/Grewer/react-hooks-store
想要基礎的實現只需要 30+ 行的代碼即可
class Modal {
private value: any;
private prevValue: any;
private reducers: (state, action) => {};
private queue: any = [];
private dispatch: (action) => void;
constructor(reducers) {
this.reducers = combineReducers(reducers)
// combineReducers 來自於 reudx ,可以引入也可以自己寫一個(后續我會寫一個庫,會包含此函數)
this.value = this.reducers({}, {})
this.dispatch = action => {
this.prevValue = this.value;
this.value = this.reducers(this.value, action)
this.onDataChange()
}
}
useModal = (deps?: string[]) => {
const [, setState] = useState(this.value);
useEffect(() => {
const index = this.queue.push({setState, deps}); // 訂閱
return () => { // 組件銷毀時取消
this.queue.splice(index - 1, 1);
};
}, []);
return [this.value, this.dispatch]
}
onDataChange = () => {
this.queue.forEach((queue) => {
const isRender = queue.deps ? queue.deps.some(dep => this.prevValue[dep] !== this.value[dep]) : true
isRender && queue.setState(this.value)
});
}
}
// 初始化 reducers
const modal = new Modal({
countReducer: function (state = 0, action) {
console.log('count Reducer', state, action)
switch (action.type) {
case "ADD":
console.log('trigger')
return state + action.payload || 1
default:
return state
}
},
listReducer: function (state = [] as any, action) {
console.log('list Reducer', state, action)
switch (action.type) {
case "ADD_LIST":
console.log('trigger')
state.push(action.payload)
return [...state]
default:
return state
}
},
personReducer: function (state = {name: 'lll', age: 18} as any, action) {
console.log('person Reducer', state, action)
switch (action.type) {
case "CHANGE_NAME":
return Object.assign({}, state, {name: action.payload})
default:
return state
}
}
})
// 導出 useModal
export const useModal = modal.useModal
簡單的使用:
function Count(props) {
const [state, dispatch] = useModal(['countReducer'])
// 非 countReducer 的更新 不會觸發此函數 render
console.warn('render Count', state, dispatch)
return <div>
<button onClick={() => dispatch({type: "ADD", payload: 2})}>+</button>
</div>
}
當然你也可以自己寫一個,自己想要的方案
總結
hooks 的存儲方案基本就這 3 類,可以用現成的,也可以使用自己寫的方案