1. Введение
React Hooks постепенно принимаются отечественными front-end командами, но схема потока данных на основе Hooks еще не отлажена, у нас есть «100» похожих вариантов, но каждый имеет свои преимущества и недостатки, что затрудняет выбор.
На этой неделе автор подробно расскажет о понимании потока данных Hooks.Я считаю, что после прочтения статьи вы сможете увидеть суть схемы потока данных Hooks, которая расцветает.
2 Интенсивное чтение
Говоря о потоке данных на основе React Hooks, начнем с базового решения, которое с наименьшей вероятностью вызовет разногласия.
Однокомпонентный поток данных
Простейший поток данных для одного компонента должен бытьuseState
:
function App() {
const [count, setCount] = useState();
}
useState
Использование его внутри компонента бесспорно, поэтому следующей темой должно быть совместное использование потока данных между компонентами.
Совместное использование потока данных между компонентами
Самое простое решение для компонентов:useContext
:
const CountContext = createContext();
function App() {
const [count, setCount] = useState();
return (
<CountContext.Provider value={{ count, setCount }}>
<Child />
</CountContext.Provider>
);
}
function Child() {
const { count } = useContext(CountContext);
}
Использование — это все официальные API, что, очевидно, не вызывает споров, но проблема в том, что данные и пользовательский интерфейс не разделены.unstated-nextРешение было найдено для вас.
Разделение потоков данных и компонентов
unstated-nextМожет помочь вам определить приведенный выше пример вApp
Данные в нем разделены, чтобы сформировать пользовательский хук управления данными:
import { createContainer } from "unstated-next";
function useCounter() {
const [count, setCount] = useState();
return { count, setCount };
}
const Counter = createContainer(useCounter);
function App() {
return (
<Counter.Provider>
<Child />
</Counter.Provider>
);
}
function Child() {
const { count } = Counter.useContainer();
}
данные иApp
Развязанный, теперьCounter
больше никогдаApp
граница,Counter
Можно комбинировать с другими компонентами.
В это время медленно всплывали проблемы с производительностью, и первое, что нужно сделать, этоuseState
Не имея возможности объединить и обновить проблему, мы, естественно, подумали об использованииuseReducer
решить.
объединить обновление
useReducer
Можно объединять и обновлять данные, что также является официальным API React, без каких-либо противоречий:
import { createContainer } from "unstated-next";
function useCounter() {
const [state, dispath] = useReducer(
(state, action) => {
switch (action.type) {
case "setCount":
return {
...state,
count: action.setCount(state.count),
};
case "setFoo":
return {
...state,
foo: action.setFoo(state.foo),
};
default:
return state;
}
return state;
},
{ count: 0, foo: 0 }
);
return { ...state, dispatch };
}
const Counter = createContainer(useCounter);
function App() {
return (
<Counter.Provider>
<Child />
</Counter.Provider>
);
}
function Child() {
const { count } = Counter.useContainer();
}
Даже если он обновляется одновременноcount
а такжеfoo
, мы также можем абстрагировать его вreducer
способ слияния обновлений.
Однако есть проблемы с производительностью:
function ChildCount() {
const { count } = Counter.useContainer();
}
function ChildFoo() {
const { foo } = Counter.useContainer();
}
возобновитьfoo
час,ChildCount
а такжеChildFoo
будет выполняться одновременно, ноChildCount
бесполезноfoo
А? Причина в том,Counter.useContainer
Предоставленный поток данных является ссылочной сущностью, чьи дочерние узлыfoo
Изменение ссылки приведет к повторному выполнению всего хука, а затем будут повторно отображены все компоненты, которые на него ссылаются.
На данный момент мы обнаружили, что можем использовать ReduxuseSelector
Внедрение обновлений по запросу.
Обновление по требованию
Во-первых, мы используем Redux для преобразования потока данных:
import { createStore } from "redux";
import { Provider, useSelector } from "react-redux";
function reducer(state, action) {
switch (action.type) {
case "setCount":
return {
...state,
count: action.setCount(state.count),
};
case "setFoo":
return {
...state,
foo: action.setFoo(state.foo),
};
default:
return state;
}
return state;
}
function App() {
return (
<Provider store={store}>
<Child />
</Provider>
);
}
function Child() {
const { count } = useSelector(
(state) => ({ count: state.count }),
shallowEqual
);
}
useSelector
может позволитьChild
существуетcount
обновлять, когда оно изменяется, иfoo
Не обновляться при изменении, что близко к идеальной цели производительности.
ноuseSelector
Функция предназначена только для предотвращения обновления компонента, когда результат вычисления не изменяется, но не гарантирует, что ссылка возвращаемого результата не изменится.
Предотвращение частых изменений ссылок на данные
Для приведенного выше сценария получитеcount
Ссылка неизменна,Но не обязательно для других сценариев.
Например:
function Child() {
const user = useSelector((state) => ({ user: state.user }), shallowEqual);
return <UserPage user={user} />;
}
Предположениеuser
Ссылка на объект меняется каждый раз, когда обновляется поток данных.,ТакshallowEqual
Конечно, это не работает, тогда мы заменяем его наdeepEqual
Как насчет глубокого контраста? В результате ссылки все равно будут меняться, но повторные рендеры будут реже:
function Child() {
const user = useSelector(
(state) => ({ user: state.user }),
// 当 user 值变化时才重渲染
deepEqual
);
// 但此处拿到的 user 引用还是会变化
return <UserPage user={user} />;
}
ты чувствуешь этоdeepEqual
При действии не запускается повторный рендеринг,user
Ссылка не изменится? Ответ заключается в том, что он изменится, потому чтоuser
Объект меняется каждый раз при обновлении потока данных,useSelector
существуетdeepEqual
Под действием действия не запускается повторный рендеринг, но поскольку глобальный редюсер скрывает собственный повторный рендеринг компонента, эта функция все равно будет выполняться повторно.user
Ссылки постоянно меняются.
следовательноuseSelector
deepEqual
должно быть сuseDeepMemo
используется в сочетании для обеспеченияuser
Ссылки меняются не часто:
function Child() {
const user = useSelector(
(state) => ({ user: state.user }),
// 当 user 值变化时才重渲染
deepEqual
);
const userDeep = useDeepMemo(() => user, [user]);
return <UserPage user={user} />;
}
Конечно это крайний случай, пока вы видитеdeepEqual
а такжеuseSelector
В то же время необходимо спросить себя, не изменится ли неожиданно ссылка на возвращаемое значение.
функция запроса кэша
Для экстремальных сценариев, даже если количество повторных рендеров и ссылка на возвращаемый результат контролируются в наибольшей степени, все равно могут возникнуть проблемы с производительностью.Последняя проблема с производительностью заключается в функции запроса.
В приведенном выше примере функция запроса относительно проста, но если функция запроса очень сложна:
function Child() {
const user = useSelector(
(state) => ({ user: verySlowFunction(state.user) }),
// 当 user 值变化时才重渲染
deepEqual
);
const userDeep = useDeepMemo(() => user, [user]);
return <UserPage user={user} />;
}
давайте предположимverySlowFunction
Чтобы пройти n 3 степени 1000 компонентов в холсте, время повторного рендеринга компонентов совершенно незначительно по сравнению со временем запроса, нам нужно рассмотреть функцию запроса кэша.
Один из способов - использоватьreselectКэширование на основе ссылок на параметры.
Представьте, еслиstate.user
цитаты меняются редко, ноverySlowFunction
очень медленно, в идеалеstate.user
Выполнить повторно после изменения ссылкиverySlowFunction
, но в приведенном выше примереuseSelector
Не знаю, что можно так оптимизировать, могу только тупо повторять каждый рендер.verySlowFunction
, даже еслиstate.user
Ничего не изменилось.
На данный момент мы хотим сказать ссылку,state.user
Является ли изменение ключом к повторному выполнению:
import { createSelector } from "reselect";
const userSelector = createSelector(
(state) => state.user,
(user) => verySlowFunction(user)
);
function Child() {
const user = useSelector(
(state) => userSelector(state),
// 当 user 值变化时才重渲染
deepEqual
);
const userDeep = useDeepMemo(() => user, [user]);
return <UserPage user={user} />;
}
В приведенном выше примере поcreateSelector
созданныйuserSelector
Будет кэшироваться слой за слоем, когда вернется первый параметрstate.user
При обращении к тому же, он напрямую вернется к первому результатам производительности, пока их использование не будет продолжать реализовывать изменения.
Это также иллюстрирует важность поддержания идемпотентности в функциональных выражениях, если
verySlowFunction
Не являясь строго идемпотентным, этот вид кэширования также не может быть реализован.
Выглядит красиво, но на практике может оказаться, что это не так уж и красиво, потому что приведенные выше примеры основаны наСелектор вообще не зависит от внешних переменных.
Кэшированные запросы в сочетании с внешними переменными
Если пользователи, которых мы хотим запросить, из разных регионов, нам нужно передатьareaId
определено, его можно разделить на две функции Selector:
import { createSelector } from "reselect";
const areaSelector = (state, props) => state.areas[props.areaId].user;
const userSelector = createSelector(areaSelector, (user) =>
verySlowFunction(user)
);
function Child() {
const user = useSelector(
(state) => userSelector(state, { areaId: 1 }),
deepEqual
);
const userDeep = useDeepMemo(() => user, [user]);
return <UserPage user={user} />;
}
Итак, чтобы не вызывать внутри функции компонентаcreateSelector
, нам нужно максимально абстрагировать использование внешних переменных в общий селектор и использовать его какcreateSelector
первый шаг.
ноuserSelector
Кэш будет недействительным, если он предоставляется нескольким компонентам, потому что мы создаем только один экземпляр Selector, поэтому эта функция также должна обернуть слой форм более высокого порядка:
import { createSelector } from "reselect";
const userSelector = () =>
createSelector(areaSelector, (user) => verySlowFunction(user));
function Child() {
const customSelector = useMemo(userSelector, []);
const user = useSelector(
(state) => customSelector(state, { areaId: 1 }),
deepEqual
);
}
Поэтому для связи объединения внешних переменных также необходимоuseMemo
а такжеuseSelector
В сочетании с,useMemo
обрабатывать кеширование ссылок для зависимостей внешних переменных,useSelector
Обработка кэширования ссылок, связанных с хранилищем.
3 Резюме
Схема потока данных, основанная на хуках, не идеальна. Когда я писал эту статью, я чувствовал, что такая схема относится к «поверхностным и глубоким». Простые сцены легко понять. По мере того, как сцены становятся более сложными, схема становится все более и более сложным.
Однако эта идея управления неизменяемым потоком данных дает разработчикам очень широкие возможности управления кешем. Пока они полностью понимают вышеизложенные концепции, они могут разработать очень «ожидаемую» модель управления кешем данных. Пока они тщательно поддерживаются, все становится очень упорядоченно.
Адрес обсуждения:Интенсивное чтение «Потока данных React Hooks» · Выпуск № 242 · dt-fe/weekly
Если вы хотите принять участие в обсуждении, пожалуйста,кликните сюда, с новыми темами каждую неделю, выходящими по выходным или понедельникам. Интерфейс интенсивного чтения — поможет вам отфильтровать надежный контент.
Сфокусируйся наАккаунт WeChat для интенсивного чтения в интерфейсе
Заявление об авторских правах: Бесплатная перепечатка - некоммерческая - не производная - сохранить авторство (Лицензия Creative Commons 3.0)