Оригинал: Как использовать useReducer в React Hooks для оптимизации производительности
гитхаб-адресДобро пожаловать в звезду!
React Hook был недоступен некоторое время, чтобы узнать о некоторых конкретных случаях использования и проблемах, которые он решает, вы можете проверить его.Danдве статьиПолное руководство по использованию эффектова такжеНапишите устойчивые компонентыВыучить больше.
В этой статье в основном представлены 6 различных способов использования useReducer в React Hooks.
предисловие
API React Hooks был официально выпущен в React V16.8. В этом блоге в основном представлены различные примеры использования useReducer. Прежде чем читать, убедитесь, что вы прочиталиОфициальное руководство по React Hooks.
Хук useReducer относится к официально расширенным хукам:
является еще одной альтернативой useState. он принимает
(state, action) => newState
, и возвращает пару с текущим состояниемdispatch
Методы. (Если вы знакомы с Redux, вы также быстро поймете, как он работает.)
Хотя useReducer — это хук-расширение, а useState — базовый хук, на самом деле useState также выполняет useReducer. Это означает, что useReducer является более родным, и вы можете заменить useReducer везде, где используете useState. Редюсеры настолько мощны, что существуют различные варианты их использования.
Далее в этой статье представлены несколько репрезентативных вариантов использования. Каждый пример представляет конкретный вариант использования и имеет связанный с ним код.
Вариант использования 1: минимальный (простой) шаблон
См. код для этого простого примера. Ниже приводится расширение этого примера подсчета.
const initialState = 0;
const reducer = (state, action) => {
switch (action) {
case 'increment': return state + 1;
case 'decrement': return state - 1;
case 'reset': return 0;
default: throw new Error('Unexpected action');
}
};
Во-первых, мы определяем initialState и редьюсер для инициализации. Обратите внимание, что состояние здесь — это просто число, а не объект. Разработчики, знакомые с Redux, могут быть сбиты с толку, но хуки подходят. Кроме того, действие — это обычная строка.
Ниже представлен компонент, использующий useReducer.
const Example01 = () => {
const [count, dispatch] = useReducer(reducer, initialState);
return (
<div>
{count}
<button onClick={() => dispatch('increment')}>+1</button>
<button onClick={() => dispatch('decrement')}>-1</button>
<button onClick={() => dispatch('reset')}>reset</button>
</div>
);
};
Когда пользователь нажимает кнопку, он отправляет действие для обновления значения счетчика, и на странице отображается обновленный счетчик. Вы можете определить сколько угодно действий в редюсере, но этот шаблон имеет ограничения, его действия ограничены.
Вот полный код:
import React, { useReducer } from 'react';
const initialState = 0;
const reducer = (state, action) => {
switch (action) {
case 'increment': return state + 1;
case 'decrement': return state - 1;
case 'reset': return 0;
default: throw new Error('Unexpected action');
}
};
const Example01 = () => {
const [count, dispatch] = useReducer(reducer, initialState);
return (
<div>
{count}
<button onClick={() => dispatch('increment')}>+1</button>
<button onClick={() => dispatch('decrement')}>-1</button>
<button onClick={() => dispatch('reset')}>reset</button>
</div>
);
};
export default Example01;
Вариант использования 2: действие — это объект
Этот пример знаком пользователям Redux. Мы использовали объект состояния и объект действия.
const initialState = {
count1: 0,
count2: 0,
};
const reducer = (state, action) => {
switch (action.type) {
case 'increment1':
return { ...state, count1: state.count1 + 1 };
case 'decrement1':
return { ...state, count1: state.count1 - 1 };
case 'set1':
return { ...state, count1: action.count };
case 'increment2':
return { ...state, count2: state.count2 + 1 };
case 'decrement2':
return { ...state, count2: state.count2 - 1 };
case 'set2':
return { ...state, count2: action.count };
default:
throw new Error('Unexpected action');
}
};
В состоянии хранятся два числа. Мы можем использовать сложные объекты для представления состояния, если редюсеры хорошо организованы (например, combReducers в react-redux). Кроме того, поскольку действие является объектом, в дополнение к значению типа вы также можете добавить к нему другие свойства, напримерaction.count
. Редьюсер в этом примере немного запутан, но это не мешает нам использовать его вот так:
const Example02 = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
<div>
{state.count1}
<button onClick={() => dispatch({ type: 'increment1' })}>+1</button>
<button onClick={() => dispatch({ type: 'decrement1' })}>-1</button>
<button onClick={() => dispatch({ type: 'set1', count: 0 })}>reset</button>
</div>
<div>
{state.count2}
<button onClick={() => dispatch({ type: 'increment2' })}>+1</button>
<button onClick={() => dispatch({ type: 'decrement2' })}>-1</button>
<button onClick={() => dispatch({ type: 'set2', count: 0 })}>reset</button>
</div>
</>
);
};
Обратите внимание, что в состоянии есть два счетчика, и определите соответствующие им типы действий, чтобы обновить их. Образец онлайн нажмитездесь
Вариант использования 3: использование нескольких useReducers
Два счетчика появляются в одном состоянии выше, что является типичным подходом к глобальному состоянию. Но нам нужно использовать только локальное (локальное) состояние, поэтому есть еще один способ использовать useReducer дважды.
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');
}
};
Здесь состояние — это число, а не объект, как в случае использования 1. Обратите внимание, что здесь действие является объектом.
Как использовать компоненты
const Example03 = () => {
const [count1, dispatch1] = useReducer(reducer, initialState);
const [count2, dispatch2] = useReducer(reducer, initialState);
return (
<>
<div>
{count1}
<button onClick={() => dispatch1({ type: 'increment' })}>+1</button>
<button onClick={() => dispatch1({ type: 'decrement' })}>-1</button>
<button onClick={() => dispatch1({ type: 'set', count: 0 })}>reset</button>
</div>
<div>
{count2}
<button onClick={() => dispatch2({ type: 'increment' })}>+1</button>
<button onClick={() => dispatch2({ type: 'decrement' })}>-1</button>
<button onClick={() => dispatch2({ type: 'set', count: 0 })}>reset</button>
</div>
</>
);
};
Как видите, у каждого счетчика есть собственный метод отправки, но общий метод редуктора. Функционал такой же, как и в варианте использования 2.
Вариант использования 4: ввод текста (TextInput)
Давайте посмотрим на реальный пример, несколько useReducers могут выполнять свои обязанности. В качестве примера возьмем собственный компонент ввода React, который хранит текстовые данные в локальном состоянии. Обновите значение текстового состояния, вызвав функцию отправки.
const initialState = '';
const reducer = (state, action) => action;
Обратите внимание, что каждый раз, когда вызывается редьюсер, старое состояние отбрасывается. Конкретное использование заключается в следующем:
const Example04 = () => {
const [firstName, changeFirstName] = useReducer(reducer, initialState);
const [lastName, changeLastName] = useReducer(reducer, initialState);
return (
<>
<div>
First Name:
<TextInput value={firstName} onChangeText={changeFirstName} />
</div>
<div>
Last Name:
<TextInput value={lastName} onChangeText={changeLastName} />
</div>
</>
);
};
Это так просто. Конечно, вы также можете добавить в него некоторую логику проверки. Полный код:
import React, { useReducer } from 'react';
const initialState = '';
const reducer = (state, action) => action;
const Example04 = () => {
const [firstName, changeFirstName] = useReducer(reducer, initialState);
const [lastName, changeLastName] = useReducer(reducer, initialState);
return (
<>
<div>
First Name:
<TextInput value={firstName} onChangeText={changeFirstName} />
</div>
<div>
Last Name:
<TextInput value={lastName} onChangeText={changeLastName} />
</div>
</>
);
};
// ref: https://facebook.github.io/react-native/docs/textinput
const TextInput = ({ value, onChangeText }) => (
<input type="text" value={value} onChange={e => onChangeText(e.target.value)} />
);
export default Example04;
Вариант использования 5: контекст
Бывают случаи, когда я хочу разделить состояние между компонентами (что понимается как реализация глобального состояния). Обычно глобальное состояние будет ограничивать повторное использование компонентов, поэтому сначала рассмотрим использование локального состояния, которое передается через пропсы (диспетч на изменение), но когда это не так удобно (понимая, что слишком много вложенных проходов), можно используйте Контекст. Если вы знакомы с Context API, нажмите, чтобы просмотретьофициальная документация.
В этом примере используется тот же редуктор, что и в варианте использования 3. Далее, давайте посмотрим, как создать контекст.
const CountContext = React.createContext();
const CountProvider = ({ children }) => {
const contextValue = useReducer(reducer, initialState);
return (
<CountContext.Provider value={contextValue}>
{children}
</CountContext.Provider>
);
};
const useCount = () => {
const contextValue = useContext(CountContext);
return contextValue;
};
useCount — это пользовательский хук, который также используется, как и другие официальные хуки. как показано ниже:
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>
);
};
contextValue
Это результат, возвращаемый useReducer, мы также переработали его с помощью хука.useCount
. Обратите внимание, что не установлено, какой контекст используется.
Наконец, используйте такой контекст:
const Example05 = () => (
<>
<CountProvider>
<Counter />
<Counter />
</CountProvider>
<CountProvider>
<Counter />
<Counter />
</CountProvider>
</>
);
Как показано выше, есть дваCountProvider
компонент, что означает наличие двух счетчиков, хотя мы используем только один контекст.
В то же самоеCountProvider
Счетчики в компонентах имеют общее состояние. Вы можете запустить этот вариант использования, чтобы увидеть, как он работает. кликните сюдаПроверять
Вариант использования 6: подписка
Первым выбором для реализации общего состояния компонента в хуках должен быть контекст, но когда уже есть общее состояние вне компонента React, как (поделиться им)? Профессиональный подход заключается в подписке на состояние прослушивания и обновлении компонента при обновлении общего состояния. Конечно, у него есть некоторые ограничения, но React официально предоставляет публичную функцию.create-subscription, который вы можете использовать для подписки.
К сожалению, этот публичный пакет методов не был переписан с помощью React Hooks, и теперь мы можем делать все возможное только с помощью хуков. Давайте реализуем ту же функциональность, что и в варианте использования 5, без использования контекста.
Сначала создайте собственный хук:
const useForceUpdate = () => useReducer(state => !state, false)[1];
Этот редьюсер просто отрицает предыдущее состояние, игнорируя действие.[1]
Просто возвращает отправку без состояния. Затем основная функция реализует общее состояние и возвращает пользовательский хук:
const createSharedState = (reducer, initialState) => {
const subscribers = [];
let state = initialState;
const dispatch = (action) => {
state = reducer(state, action);
subscribers.forEach(callback => callback());
};
const useSharedState = () => {
const forceUpdate = useForceUpdate();
useEffect(() => {
const callback = () => forceUpdate();
subscribers.push(callback);
callback(); // in case it's already updated
const cleanup = () => {
const index = subscribers.indexOf(callback);
subscribers.splice(index, 1);
};
return cleanup;
}, []);
return [state, dispatch];
};
return useSharedState;
};
Мы использовали useEffect. Это очень важный хук, вам нужно внимательно прочитать официальную документацию, чтобы научиться его использовать. В useEffect мы подписываемся на функцию обратного вызова, чтобы принудительно обновить компонент. Подписку необходимо очищать при уничтожении компонента.
Далее мы можем создать два состояния с общим состоянием. Используя тот же редюсер и начальное значение initialState, что и в варианте использования 5, вариант использования 3:
const useCount1 = createSharedState(reducer, initialState);
const useCount2 = createSharedState(reducer, initialState);
Это отличается от варианта использования 5, эти два хука привязаны к определенному общему состоянию. Затем используем эти два крючка.
const Counter = ({ count, dispatch }) => (
<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 Counter1 = () => {
const [count, dispatch] = useCount1();
return <Counter count={count} dispatch={dispatch} />
};
const Counter2 = () => {
const [count, dispatch] = useCount2();
return <Counter count={count} dispatch={dispatch} />
};
Обратите внимание, что компонент Counter является обычным компонентом без сохранения состояния. Используйте так:
const Example06 = () => (
<>
<Counter1 />
<Counter1 />
<Counter2 />
<Counter2 />
</>
);
Как видите, мы не использовали Context, но также добились общего состояния. Каждый должен подробно изучить useReducer, который очень полезен для оптимизации производительности.
Весь код в тексте находится вздесь, чтобы увидеть онлайн-пример, нажмите здесьПроверять.
Некоторые из моих собственных выводов:
- У хуков есть свои реквизиты и состояние каждый раз при рендеринге. Можно считать, что содержимое каждого рендера будет формировать снимок и сохраняться (Функция уничтожается, но переменная сохраняется при реакции), поэтому при изменении состояния и рендеринга формируется N состояний рендеринга, и каждое состояние рендеринга имеет свои фиксированные реквизиты и состояние. Это тоже особенность функционала — неизменность данных
- Вопросы производительности Хотя параметр функции useState является начальным значением, так как вся функция Render, она будет вызываться каждый раз при ее инициализации.Если расчет начального значения занимает очень много времени, рекомендуется использовать функцию для передачи в , так что он будет выполнен только один раз:
- Если вы знакомы с жизненным циклом компонентов класса React, вы можете подумать, что
useEffect Hook
это комбинацияcomponentDidMount, componentDidUpdate, 以及 componentWillUnmount(在useEffect的回调中)
, но есть разница, useEffect не мешает браузеру обновлять экран- Хуки объединяют связанную логику для унифицированной обработки вместо разделения логики в соответствии с жизненным циклом.
- useEffect срабатывает после рендеринга браузера.Если вы хотите срабатывать синхронно при изменении DOM, вам нужно использовать useLayoutEffect, который срабатывает синхронно с изменениями DOM перед перерисовкой браузера. Однако попробуйте использовать стандартный useEffect для других макетов, чтобы не блокировать обновление представления.
```
function FunctionComponent(props) {
const [rows, setRows] = useState(() => createRows(props.count));
}
useRef 不支持这种特性,需要写一些[冗余的函判定是否进行过初始化。](https://reactjs.org/docs/hooks-faq.html#how-to-create-expensive-objects-lazily)
```
Если есть какие-либо ошибки или неточности, пожалуйста, обязательно исправьте их, большое спасибо!