Я думаю, что после выхода Hook многие друзья захотят его попробовать.
Длина статьи немного длинная, но, пожалуйста, будьте терпеливы~
Хук-функция: функция обратного вызова, срабатывающая на определенном этапе. Пример: функция жизненного цикла Vue — это функция-ловушка.
Если рабочий хочет хорошо работать, он должен сначала заточить свои инструменты.
Давайте углубимся в хуки, встроенные в React.
Здесь мы просто помечаем несколько хуков
- useState [состояние обслуживания]
- USEFFECT [Завершенные] Побочные эффекты операции
- useContext [использовать общее состояние]
- useReducer [похоже на приведение]
- useCallback [кэш-функция]
- useMemo [значение кеша]
- useRef [Доступ к DOM]
- useImperativeHandle [с использованием значения/метода, предоставляемого дочерним компонентом]
- useLayoutEffect [Завершение операции с побочными эффектами блокирует отрисовку в браузере]
Далее, давайте более подробно рассмотрим эти 9 крючков один за другим.
useState
Состояние нормального обновления/функционального обновления
const Index = () => {
const [count, setCount] = useState(0);
const [obj, setObj] = useState({ id: 1 });
return (
<>
{/* 普通更新 */}
<div>count:{count}</div>
<button onClick={() => setCount(count + 1)}>add</button>
{/* 函数式更新 */}
<div>obj:{JSON.stringify(obj)}</div>
<button
onClick={() =>
setObj((prevObj) => ({ ...prevObj, ...{ id: 2, name: "张三" } }))
}
>
merge
</button>
</>
);
};
useEffect
useEffet мы можем понять, что он заменяетcomponentDidMount
, componentDidUpdate
, componentWillUnmount
Эти три жизненных цикла, но он все же более мощный.
Этот крючок важнее, и нам нужно время, чтобы освоить его.
- Структура кода с 3 жизненными циклами
useEffect(
() => {
// 这里的代码块 等价于 componentDidMount
// do something...
// return的写法 等价于 componentWillUnmount
return () => {
// do something...
};
},
// 依赖列表,当依赖的值有变更时候,执行副作用函数,等价于 componentDidUpdate
[ xxx,obj.xxx ]
);
Примечание. Список зависимостей является гибким и может быть записан тремя способами.
- когда массив пуст
[ ]
, указывающее, что метод обратного вызова не должен выполняться для изменения состояния страницы [то есть только при инициализации,componentDidMount
], - Когда этот параметр не передается, это означает, что метод обратного вызова будет выполнен после изменения любого состояния страницы.
- Когда массив не пуст, после изменения значения в массиве будет выполнен метод обратного вызова.
- Мы также столкнемся с некоторыми сценариями, такими как:
- Сценарий 1: я завишу от некоторого значения,Но я не хочу выполнять метод обратного вызова при инициализации, я хочу изменить зависимость, а затем выполнить метод обратного вызова
Здесь мы используем хук useRef:
const firstLoad = useRef(true);
useEffect(() => {
if (firstLoad.current) {
firstLoad.current = false;
return;
}
// do something...
}, [ xxx ]);
- Сценарий 2: у меня есть метод асинхронного запроса getData, я хочу, чтобы он вызывался во время инициализации, а также мог вызываться при нажатии кнопки
Давайте сначала напишем это
// ...
const getData = async () => {
const data = await xxx({ id: 1 });
setDetail(data);
};
useEffect(() => {
getData();
}, []);
const handleClick = () => {
getData();
};
// ...
Но сообщил предупреждение:
Line 77:6: React Hook useEffect has a missing dependency: 'getData'.
Either include it or remove the dependency array
react-hooks/exhaustive-deps
Смысл ошибки: мне нужно использовать useEffect для добавления зависимостей getData
Это правило хука, поэтому мы меняем его следующим образом:
// ...
const getData = async () => {
const data = await xxx({ id: 1 });
setDetail(data);
};
useEffect(() => {
getData();
}, [getData]);
const handleClick = () => {
getData();
};
// ...
Но было сообщено другое предупреждение:
Line 39:9: The 'getData' function makes the dependencies of useEffect Hook (at line 76) change on every render.
Move it inside the useEffect callback.
Alternatively, wrap the 'getData' definition into its own useCallback() Hook react-hooks/exhaustive-deps
Смысл ошибки таков: пока компонент обновляется и рендер срабатывает, getData будет переопределяться.. Ссылка в это время другая, что приведет к запуску useEffect.
Это поведение влияет на производительность, мы используем хук useCallback, чтобы кэшировать его для повышения производительности:
// ...
const getData = useCallback(async () => {
const data = await xxx({ id: 1 });
setDetail(data);
}, []);
useEffect(() => {
getData();
}, [getData]);
const handleClick = () => {
getData();
};
// ...
Это всего лишь пример, в основном для иллюстрации мышления, которое возникает из-за подсказки об ошибке. Конечно, вы также можете использовать комментарий, чтобы закрыть eslint или напрямую закрыть правила eslint, что в основном зависит от вашего выбора.
использовать// eslint-disable-next-line react-hooks/exhaustive-deps
,Такие как:
// ...
const [count, setCount] = useState(1);
const xxx = () => {};
useEffect(() => {
// use count do something...
console.log(count);
xxx();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// ...
useContext
Контекст предоставляет способ обмена такими значениями между компонентами без необходимости явного просмотра дерева компонентов. пропускать реквизит слой за слоем
пример
const obj = {
value: 1
};
const obj2 = {
value: 2
};
const ObjContext = React.createContext(obj);
const Obj2Context = React.createContext(obj2);
const App = () => {
return (
<ObjContext.Provider value={obj}>
<Obj2Context.Provider value={obj2}>
<ChildComp />
</Obj2Context.Provider>
</ObjContext.Provider>
);
};
// 子级
const ChildComp = () => {
return <ChildChildComp />;
};
// 孙级或更多级
const ChildChildComp = () => {
const obj = useContext(ObjContext);
const obj2 = useContext(Obj2Context);
return (
<>
<div>{obj.value}</div>
<div>{obj2.value}</div>
</>
);
};
useReducer
В некоторых сценариях useReducer подходит больше, чем useState, когда логика состояния более сложная. Мы можем использовать этот хук вместо useState, он работает как Redux, смотрите пример:
const initialState = [
{ id: 1, name: "张三" },
{ id: 2, name: "李四" }
];
const reducer = (state: any, { type, payload }: any) => {
switch (type) {
case "add":
return [...state, payload];
case "remove":
return state.filter((item: any) => item.id !== payload.id);
case "update":
return state.map((item: any) =>
item.id === payload.id ? { ...item, ...payload } : item
);
case "clear":
return [];
default:
throw new Error();
}
};
const List = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
List: {JSON.stringify(state)}
<button
onClick={() =>
dispatch({ type: "add", payload: { id: 3, name: "周五" } })
}
>
add
</button>
<button onClick={() => dispatch({ type: "remove", payload: { id: 1 } })}>
remove
</button>
<button
onClick={() =>
dispatch({ type: "update", payload: { id: 2, name: "李四-update" } })
}
>
update
</button>
<button onClick={() => dispatch({ type: "clear" })}>clear</button>
</>
);
};
Открытый тип может дать нам лучшее понимание того, что мы делаем в данный момент.
useCallback
// 除非 `a` 或 `b` 改变,否则不会变
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
Скользя к вышесказанному, уже упоминался пример, когда дело доходит до useCallback , это сцена, Мы все знаем, что его можно использовать для кэширования функции.
Далее мы поговорим о другом сценарии.
Как я уже говорил: пока рендеринг родительского компонента выполняется в реакции, дочерняя группа будет запущена по умолчанию. Рендеринг, реакция предоставляет несколько методов, чтобы избежать накладных расходов на производительность этого повторного рендеринга:
React.PureComponent
,React.memo
,shouldComponentUpdate()
Давайте терпеливо рассмотрим пример, когда наш дочерний компонент принимает свойство как метод, например:
const Index = () => {
const [count, setCount] = useState(0);
const getList = (n) => {
return Array.apply(Array, Array(n)).map((item, i) => ({
id: i,
name: "张三" + i
}));
};
return (
<>
<Child getList={getList} />
<button onClick={() => setCount(count + 1)}>count+1</button>
</>
);
};
const Child = ({ getList }) => {
console.log("child-render");
return (
<>
{getList(10).map((item) => (
<div key={item.id}>
id:{item.id},name:{item.name}
</div>
))}
</>
);
};
Попробуем интерпретировать, когда нажимается кнопка «count+1», происходит что-то вроде этого:
父组件render > 子组件render > 子组件输出 "child-render"
Чтобы избежать ненужного рендеринга подкомпонентов, мы используемReact.memo
,Такие как:
// ...
const Child = React.memo(({ getList }) => {
console.log("child-render");
return (
<>
{getList(10).map((item) => (
<div key={item.id}>
id:{item.id},name:{item.name}
</div>
))}
</>
);
});
// ...
Недолго думая, когда мы нажмем «count+1», дочерний компонент не будет повторно отображаться. но реальность Да, он все еще рендерится, почему? Ответ: React.memo будет выполнять только поверхностное сравнение свойств, то есть он будет передан после повторного рендеринга родительского компонента. Различные ссылочные методы
getList
, после неглубокого сравнения он не равен, в результате чего дочерний компонент все еще отображается.
В это время можно использовать useCallback, он может кэшировать функцию, и когда зависимость не изменилась, он всегда будет возвращать одну и ту же ссылку. Такие как:
// ...
const getList = useCallback((n) => {
return Array.apply(Array, Array(n)).map((item, i) => ({
id: i,
name: "张三" + i
}));
}, []);
// ...
Резюме: если дочерний компонент принимает метод в качестве атрибута, нам нужно использовать useCallback, когда мы используем React.memo, чтобы избежать ненужного рендеринга дочернего компонента, иначе React.memo будет бессмысленным.
useMemo
Подобно вычислению vue, оно в основном используется, чтобы избежать некоторых дорогостоящих вычислений при каждом рендеринге, для простого примера.
Независимо от того, сколько раз отрисовывалась страница, отметка времени не изменится, поскольку она уже кэширована, если только не изменятся зависимости.
// ...
const getNumUseMemo = useMemo(() => {
return `${+new Date()}`;
}, []);
// ...
useRef
Мы используем его для доступа к DOM, чтобы манипулировать DOM, например, щелкнуть кнопку, чтобы сфокусировать текстовое поле:
const Index = () => {
const inputEl = useRef(null);
const handleFocus = () => {
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={handleFocus}>Focus</button>
</>
);
};
Примечание. Возвращенный объект ссылки сохраняется на протяжении всего времени существования компонента. Это похоже на атрибут экземпляра класса, и мы используем его. Прокрутите вверх и посмотрите на приведенный выше пример с useRef .
Только что упомянутый пример касается доступа к DOM Что, если мы хотим получить доступ к компоненту и манипулировать определенным DOM в компоненте? Нам нужно использовать высокоуровневый компонент React.forwardRef для пересылки ссылки, например:
const Index = () => {
const inputEl = useRef(null);
const handleFocus = () => {
inputEl.current.focus();
};
return (
<>
<Child ref={inputEl} />
<button onClick={handleFocus}>Focus</button>
</>
);
};
const Child = forwardRef((props, ref) => {
return <input ref={ref} />;
});
useImperativeHandle
useImperativeHandle позволяет нам вызывать подсборки родительского компонента с открытыми свойствами/методами. Такие как:
const Index = () => {
const inputEl = useRef();
useEffect(() => {
console.log(inputEl.current.someValue);
// test
}, []);
return (
<>
<Child ref={inputEl} />
<button onClick={() => inputEl.current.setValues((val) => val + 1)}>
累加子组件的value
</button>
</>
);
};
const Child = forwardRef((props, ref) => {
const inputRef = useRef();
const [value, setValue] = useState(0);
useImperativeHandle(ref, () => ({
setValue,
someValue: "test"
}));
return (
<>
<div>child-value:{value}</div>
<input ref={inputRef} />
</>
);
});
Резюме: аналогично vue, использующему флаг ref для компонента, а затем this.$refs.xxx для управления dom или вызова значения/метода подкомпонента, просто реагируйте «представлено двумя хуками».
useLayoutEffect
Эффект вызывается синхронно после всех изменений DOM. Вы можете использовать его для чтения макета DOM и синхронизации Запустить повторный рендеринг. Прежде чем браузер выполнит рисование, расписание обновления внутри useLayoutEffect будет обновлено с тем же пошаговое обновление, что означает, что он блокирует рисование в браузере. Поэтому используйте useEffect, когда это возможно, чтобы избежать блокировки. Подключи визуальное обновление.
Нажми на меня, посмотри пример, вдруг понял
Резюме: Следующей эрой интерфейса будет эра хуков.Для студентов, которым нравится React, давайте хорошо использовать эти хуки, чтобы быть непобедимыми.