React 18 — useSyncExternalStore

Milk Midi
8 min readMay 3, 2022

--

大家好,我是奶綠茶
今天來分享 React 18 新的 hooks — useSyncExternalStore

之前如果要跨組件溝通,可以使用 React Context, 但他會有強制 rerender 的問題。
或是使用 redux 這樣的套件,現在可以直接用 useSyncExternalStore 來取代。
(如果是超大專案要使用,奶綠我還是覺得導入 redux 更適合)
來人呀,先上 Code。

官網文件:
https://reactjs.org/docs/hooks-reference.html#usesyncexternalstore

// 基本用法,直接拿到 store 的值。
const state = useSyncExternalStore(
store.subscribe, store.getSnapshot);
// 或是 selector 你想要的值
const selectedField = useSyncExternalStore(
store.subscribe, () => store.getSnapshot().selectedField);
背後都會用 Object.is 來幫你判斷該 Component 要不要 rerender。

Example1

store.js 寫法

const store = {
state: {
count: 0,
text: "milkmidi",
someData: ["vue", "react"]
},
setState: (fnOrState) => {
const newState =
typeof fnOrState === "function"
? fnOrState(store.state)
: fnOrState;
store.state = {
...store.state,
...newState
};
store.listeners.forEach((listener) => {
listener();
});
},
listeners: new Set(),
subscribe: (callback) => {
store.listeners.add(callback);
return () => {
store.listeners.delete(callback);
};
},
getSnapshot: () => store.state
};
export default store;

要取值、更新值,只需要這樣寫

import store from './store';
cont App = ()=> {
// 取 state 的值
const state = useSyncExternalStore(
store.subscribe,
store.getSnapshot);
return (
<button
// 更新
onClick={()=> { store.setState({count: 9999})} }
>increment</button>
)
}

收工,超方便的。

Example2

需有多個 store 的話,就再包裝一下。

// createStore.js
const createStore = (initialState) => {
let state = initialState;
const listeners = new Set();
const getSnapshot = () => state;
const setState = (fnOrState) => {
const newState =
typeof fnOrState === "function"
? fnOrState(state)
: fnOrState;
state = {
...state,
...newState
};
listeners.forEach((listener) => listener());
};
const subscribe = (listener) => {
listeners.add(listener);
return () => {
listeners.delete(listener);
};
};
return {
getSnapshot,
setState,
subscribe
};
};
export default createStore;

建立新的 store, 並包裝成 react hooks

// useMyStore.js
import { useSyncExternalStore } from "react";
import createStore from "./createStore";
const initialState = {
count: 0,
text: "milkmidi"
};
const store = createStore(initialState);const loop = (v) => v;
export default function useMyStore(selector = loop) {
return useSyncExternalStore(
store.subscribe, () => selector(store.getSnapshot())
);
}
export const { setState } = store;

Component 端

import useMyStore, { setState } from './useMyStore';
cont App = ()=> {
const state = useMyStore();
return (
<button
onClick={()=> { setState({count: 9999}) }}
>increment</button>
)
}

Example3 reducer

不過上面的寫法,都不夠抽象化,這時可以導入 reducer

// createReducerStore.js
const createReducerStore = (reducer, initialState) => {
let state = initialState;
const listeners = new Set();
const getSnapshot = () => state;
// 換成 dispatch
const dispatch = (action) => {
state = reducer(state, action); // 這裡換成 reducer
listeners.forEach((listener) => listener());
};
const subscribe = (listener) => {
listeners.add(listener);
return () => {
listeners.delete(listener);
};
};
return {
getSnapshot,
dispatch,
subscribe
};
};
export default createReducerStore;

建立 useReducerStore

import { useSyncExternalStore } from "react";
import createReducerStore from "./createReducerStore";
const initialState = {
count: 0,
text: "milkmidi"
};
const reducer = (state, action) => {
switch (action.type) {
case "increment":
return {
...state,
count: state.count + 1
};
case "text":
return {
...state,
text: action.payload
};
default:
return state;
}
};
const store = createReducerStore(reducer, initialState);const loop = (v) => v;
export default function useTodoStore(selector = loop) {
return useSyncExternalStore(store.subscribe, () =>
selector(store.getSnapshot())
);
}
export const { dispatch } = store;

Component 端

import useReducerStore, { dispatch } from "./useReducerStore";cont App = ()=> {
const state = useReducerStore();
return (
<button
onClick={()=> { dispatch({type: 'increment'}) }}
>increment</button>
)
}

React17 要用的話,官方也有推出 npm 套件可以用,原始碼滿值得讀的
https://github.com/facebook/react/blob/main/packages/use-sync-external-store/src/useSyncExternalStoreShimClient.js

附上原始碼,祝大家學習愉快
https://codesandbox.io/s/react-18-usesyncexternalstore-9xuuzg?file=/src/App.jsx

--

--