В последнее время React Hooks в основном используются во всех проектах, и исторические проекты также были переписаны с помощью React Hooks.По сравнению с классовыми компонентами преимущества React Hooks можно суммировать в одном предложении: это просто, и нет сложной жизни. в хуках React.Cycle, никаких сложных указаний в компонентах класса, никаких сложных шаблонов повторного использования компонентов, таких как HOC, реквизиты рендеринга и т. д. Эта статья в основном обобщает опыт разработки хуков React.
- Рендеринг поведения в хуках React
- Оптимизация производительности в хуках React
- Управление состоянием и связь в хуках React
Оригинальный текст был впервые опубликован в моем блоге:GitHub.com/fort и все…
1. Рендеринг поведения в хуках React
1. Как визуализируются компоненты React hooks
Ключом к пониманию хуков React является понимание того, что каждый рендеринг компонента хуков независим, и каждый рендер — это независимая область со своими собственными реквизитами и состояниями, обработчиками событий и т. д. В двух словах:
Каждый рендеринг является несвязанной функцией с полностью независимой областью действия, выполняет функцию рендеринга и возвращает соответствующий результат рендеринга.
Компонент класса отличается, реквизиты и состояния в компоненте класса указывают на последний рендеринг на протяжении всего жизненного цикла.
Разница между поведением рендеринга компонента React hooks и компонента класса выглядит очень запутанной, мы можем использовать диаграмму, чтобы различить ее.
На приведенном выше рисунке показан процесс рендеринга компонента React hooks.Как видно из рисунка, каждый рендеринг компонента реагирующих хуков представляет собой независимую функцию, которая будет генерировать свойства и состояние, специфичные для области рендеринга.Затем посмотрите на рендеринг в поведении компонента класса:
В компоненте класса реквизит и состояние будут генерироваться в конструкторе компонента класса при запуске рендеринга.Все процессы рендеринга выполняются в функции рендеринга, и при каждом рендеринге не будет генерироваться новое состояние. значения для this.props и this.state, которые были изначально инициализированы.
2. Обратите внимание на поведение рендеринга хуков React в проекте.
Понимание поведения рендеринга хуков React показывает, как мы можем использовать их в проекте. Прежде всего, потому что компонент React hooks будет генерировать независимый домен во время каждого процесса рендеринга, следовательно, подфункции и переменные внутри компонента будут регенерироваться каждый раз, когда он живет, поэтому мы должны уменьшить объявление внутри функции компонента React hooks. .
Написание одного:
function App() {
const [counter, setCounter] = useState(0);
function formatCounter(counterVal) {
return `The counter value is ${counterVal}`;
}
return (
<div className="App">
<div>{formatCounter(counter)}</div>
<button onClick={() => setCounter(prevState => ++prevState)}>
Increment
</button>
</div>
);
}
Пишем два:
function formatCounter(counterVal) {
return `The counter value is ${counterVal}`;
}
function App() {
const [counter, setCounter] = useState(0);
return (
<div className="App">
<div>{formatCounter(counter)}</div>
<button onClick={()=>onClick(setCounter)}>
Increment
</button>
</div>
);
}
Компонент приложения является компонентом хуков Мы знаем поведение рендеринга хуков React, поэтому написание метода 1 будет переопределять функцию formatCounter каждый раз, когда он рендерится, поэтому это нежелательно. Мы рекомендуем писать способ 2. Если функция не связана с состоянием и пропсами в компоненте, ее можно объявить вне компонента. Если функция сильно связана с состоянием и реквизитами в компоненте, то в следующем разделе мы представим методы useCallback и useMemo.
Состояние и пропсы в хуках React регенерируются и независимы в процессе каждого рендера, поэтому если нам нужен объект, то он должен быть неизменным от начала до рендера1, рендера2,… Как это сделать. (Константа здесь в том, что он не будет регенерироваться, это означает, что указанный адрес остается неизменным, и его значение может быть изменено)
Мы можем использовать useRef для создания «константы», которая всегда указывает на один и тот же адрес ссылки во время рендеринга компонента.
С помощью useRef можно реализовать множество функций, таких как получение состояния в предыдущем рендеринге во время определенного рендеринга.
function App(){
const [count,setCount] = useState(0)
const prevCount = usePrevious(count);
return (
<div>
<h1>Now: {count}, before: {prevCount}</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
В приведенном выше примере объект ref, который мы создали с помощью useRef(), является одним и тем же объектом на протяжении всего цикла использования компонента usePrevious Мы можем записать компонент App во время процесса рендеринга компонента App, обновив значение ref.current. состояние предыдущего рендера в рендере.
Здесь есть еще место, которое не легко понять, давайте посмотрим на работу
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
Возникает вопрос: почему при изменении значения возвращаемый ref.current указывает на значение до изменения значения?
То есть:
Почему useEffect выполняется только после возврата ref.current?
Чтобы объяснить это, давайте поговорим о магическом useEffect.
3. Магическое использованиеЭффект
Каждую отрисовку компонента хуков можно рассматривать как независимую функцию render1, render2... rendern, так как же связаны эти функции отрисовки, и вопрос в предыдущем разделе, почему в usePrevious useEffect возвращает Executed после ref.current. С этими двумя вопросами давайте взглянем на самый волшебный useEffect в компоненте hooks.
Резюме с предложением:
Каждый рендеринг будет генерировать другую функцию рендеринга, и каждый рендеринг будет генерировать другой эффект с помощью useEffect, и эффекты будут звучать после каждого рендеринга.
В дополнение к созданию различных областей видимости для каждого рендеринга, если в компоненте ловушек используется useEffect, через useEffect будет создан уникальный эффект, который вступит в силу после завершения рендеринга.
Например:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
В приведенном выше примере завершенная логика:
- Визуализировать начальный контент:
<p>You clicked 0 times</p>
- Этот эффект вызывается после завершения рендеринга: { document.title = 'Вы нажали 0 раз' }.
- Нажмите Нажмите на меня
- Рендеринг нового контента Рендеринг контента:
<p>You clicked 1 times</p>
- Вызовите этот эффект после рендеринга:() => { document.title = 'Вы нажали 1 раз' }.
То есть при каждом рендеринге эффект находится в конце очереди синхронного выполнения и выполняется после обновления dom или возврата функции.
Мы рассматриваем пример usePrevious:
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
Из-за механизма useEffect в новом процессе рендеринга сначала возвращается ref.current, а затем выполняется зависимое от deps обновление ref.current, поэтому usePrevios всегда возвращает последнее значение.
Теперь мы знаем, что в рендере есть свое независимое состояние, пропсы и независимые области действия функций, определения функций, эффекты и т. д. На самом деле в каждом рендере почти все независимо. Наконец, давайте рассмотрим два примера:
(1)
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
setTimeout(() => {
console.log(`You clicked ${count} times`);
}, 3000);
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
(2)
function Counter() {
const [count, setCount] = useState(0);
setTimeout(() => {
console.log(`You clicked ${count} times`);
}, 3000);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
В этих двух примерах мы нажимаем кнопку «Нажми меня» 5 раз в течение 3, после чего выходные результаты совпадают.
You clicked 0 times You clicked 1 times You clicked 2 times You clicked 3 times You clicked 4 times You clicked 5 times
В целом рендер каждого рендера практически независим и уникален, кроме объекта, созданного с помощью useRef, другие объекты и функции не имеют никакой корреляции.
2. Оптимизация производительности в хуках React
Ранее мы говорили о поведении рендеринга в хуках React, а также предварительном Упоминается, что функции, не связанные с состоянием и пропсами, объявление их за пределами компонента хуков может повысить производительность компонента и уменьшить повторное объявление неактуальной функции в каждом рендеринге.Кроме того, хуки React также предоставляют useMemo и useCallback для оптимизации производительность компонента.
(1).useCallback
Иногда нам приходится определять функции или методы в компоненте хуков, поэтому рекомендуется использовать useCallback для кэширования этого метода.Когда зависимости useCallback не меняются, функцию не нужно повторно объявлять во время каждого процесса рендеринга.
useCallback принимает два параметра, первый параметр — это функция, которую нужно кэшировать, а второй параметр — это массив, представляющий зависимость.При изменении зависимости он повторно объявит новую функцию, в противном случае он вернет кешированную функцию.
function formatCounter(counterVal) {
return `The counter value is ${counterVal}`;
}
function App(props) {
const [counter, setCounter] = useState(0);
const onClick = useCallback(()=>{
setCounter(props.count)
},[props.count]);
return (
<div className="App">
<div>{formatCounter(counter)}</div>
<button onClick={onClick}>
Increment
</button>
</div>
);
}
В приведенном выше примере мы добавили метод onClick на основе примера из главы 1 и закэшировали этот метод.Только когда счетчик в свойствах изменится, нам нужно перегенерировать этот метод.
(2).useMemo
useMemo похож на useCallback, разница в том, что useMemo кеширует не функции, а объекты (которые могут быть jsx объектами виртуального dom), аналогично, когда зависимости остаются неизменными, возвращается кешированный объект, в противном случае регенерируется новый объект.
Для оптимизации производительности компонентов мы рекомендуем:
Любой метод, объявленный в компоненте хуков реакции, или любой объект должны быть обернуты в useCallback или useMemo.
(3) Метод сравнения useCallback, зависимости useMemo
Давайте посмотрим, как сравниваются зависимости useCallback и useMemo до и после обновления.
import is from 'shared/objectIs';
function areHookInputsEqual(
nextDeps: Array<mixed>,
prevDeps: Array<mixed> | null,
) {
if (prevDeps === null) {
return false;
}
if (nextDeps.length !== prevDeps.length) {
return false
}
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}
Метод is определяется как:
function is(x: any, y: any) {
return (
(x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y)
);
}
export default (typeof Object.is === 'function' ? Object.is : is);
Это метод является сопоставлением сопоставления объекта. - ES6, то есть до и после зависимости в USECallback и USEMEMO одинаково, это неглубокое сравнение.
3. Управление состоянием и коммуникация в хуках React
Управление локальным состоянием в реактивных хуках короче, чем в компонентах класса, поэтому, если наши компоненты используют реагирующие хуки, как решить проблему связи между компонентами.
(1) UseContext
Самая основная идея может состоять в том, чтобы решить проблему связи между компонентами через useContext.
Например:
function useCounter() {
let [count, setCount] = useState(0)
let decrement = () => setCount(count - 1)
let increment = () => setCount(count + 1)
return { count, decrement, increment }
}
let Counter = createContext(null)
function CounterDisplay() {
let counter = useContext(Counter)
return (
<div>
<button onClick={counter.decrement}>-</button>
<p>You clicked {counter.count} times</p>
<button onClick={counter.increment}>+</button>
</div>
)
}
function App() {
let counter = useCounter()
return (
<Counter.Provider value={counter}>
<CounterDisplay />
<CounterDisplay />
</Counter.Provider>
)
}
В этом примере с помощью createContext и useContext контекст можно использовать в CounterDisplay, подкомпоненте приложения, чтобы реализовать взаимодействие компонентов в определенном смысле.
Кроме того, на основе useContext для его целостности в отрасли также есть несколько относительно простых пакетов:
GitHub.com/Джейми строит… GitHub.com/Диего и передние стойки/co…
Но суть его не решает проблемы:
Если контекстов слишком много, как поддерживать эти контексты
Иными словами, в сценарии с большим количеством взаимодействий компонентов удобочитаемость кода взаимодействия компонентов с использованием контекста очень плохая. Сцена этого компонента класса та же самая, контекст не новинка, хотя использование useContext снижает сложность использования контекста.
(2) Redux объединяет хуки для реализации связи между компонентами.
Связь между компонентами hooks также может быть реализована с помощью Redux. То есть:
В хуках React редукс также имеет свое значение.
Существует проблема с хуками, потому что нет компонента более высокого порядка, похожего на connect в react-redux для передачи mapState и mapDispatch, решение — использовать redux-react-hook или хуки 7.1 версии react-redux.
- redux-react-hook
StoreContext, useDispatch и useMappedState предоставляются в redux-react-hook для управления хранилищем в redux.Например, способ определить mapState и mapDispatch:
import {StoreContext} from 'redux-react-hook';
ReactDOM.render(
<StoreContext.Provider value={store}>
<App />
</StoreContext.Provider>,
document.getElementById('root'),
);
import {useDispatch, useMappedState} from 'redux-react-hook';
export function DeleteButton({index}) {
// Declare your memoized mapState function
const mapState = useCallback(
state => ({
canDelete: state.todos[index].canDelete,
name: state.todos[index].name,
}),
[index],
);
// Get data from and subscribe to the store
const {canDelete, name} = useMappedState(mapState);
// Create actions
const dispatch = useDispatch();
const deleteTodo = useCallback(
() =>
dispatch({
type: 'delete todo',
index,
}),
[index],
);
return (
<button disabled={!canDelete} onClick={deleteTodo}>
Delete {name}
</button>
);
}
- Хуки версии react-redux 7.1
Это также официально рекомендуется.Hooks-версия react-redux предоставляет три основных метода: useSelector(), useDispatch() и useStore(), которые соответствуют mapState, mapDispatch и напрямую получают экземпляр хранилища в redux.
Кратко представим useSelector.Помимо получения состояния из хранилища, useSelector также поддерживает функцию глубокого сравнения.Если соответствующее состояние не менялось до и после, оно не будет пересчитано.
Например, самое основное использование:
import React from 'react'
import { useSelector } from 'react-redux'
export const TodoListItem = props => {
const todo = useSelector(state => state.todos[props.id])
return <div>{todo.text}</div>
}
Использование для реализации функции кеша:
import React from 'react'
import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'
const selectNumOfDoneTodos = createSelector(
state => state.todos,
todos => todos.filter(todo => todo.isDone).length
)
export const DoneTodosCounter = () => {
const NumOfDoneTodos = useSelector(selectNumOfDoneTodos)
return <div>{NumOfDoneTodos}</div>
}
export const App = () => {
return (
<>
<span>Number of done todos:</span>
<DoneTodosCounter />
</>
)
}
В приведенном выше использовании кеша, пока todos.filter(todo => todo.isDone).length не изменяется, он не будет пересчитан.