В этой статье дается подробное объяснение новых функций хуков, выпущенных React после версии 16.8, и демонстрируется код некоторых часто используемых хуков в надежде оказать некоторую помощь тем, кто в ней нуждается.
предисловие
Эта статья была включена вGithub
: GitHub.com/Умышленно миром/…, добро пожаловать Звезда!
1. Введение в хуки
Hooks
даReact v16.7.0-alpha
Добавлены новые функции в . это позволяет вамclass
использовать кромеstate
и другиеReact
характеристика.
Эта статья предназначена для демонстрации различныхHooks API
Использование метода, внутренний принцип здесь подробно описываться не будут.
Во-вторых, первый опыт крючков
Foo.js
import React, { useState } from 'react';
function Foo() {
// 声明一个名为“count”的新状态变量
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
export default Foo;
useState
только одинHook
, можно использовать, когда мы не используемclass
В случае компонента имеет свой собственныйstate
, и может быть изменен с помощьюstate
контролироватьUI
дисплей.
3. Два часто используемых хука
1. состояние использования
грамматика
const [state, setState] = useState(initialState)
- Передайте единственный параметр:
initialState
, которые могут быть числами, строками и т. д., объектами или массивами. - Возвращается массив из двух элементов: первый элемент,
state
Переменная,setState
Исправлятьstate
метод стоимости.
с использованием в классеsetState
Сходства и различия:
- Та же самая точка: вызывается несколько раз в одном цикле рендеринга
setState
, данные изменяются только один раз. - Отличие: в классе
setState
является слиянием, а в функциональном компонентеsetState
является заменой.
использовать контраст
Прежде чем вы захотите использовать состояние внутри компонента, вы должны использоватьclass
компоненты, такие как:
Foo.js
import React, { Component } from 'react';
export default class Foo extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
Теперь мы можем сделать то же самое с функциональными компонентами. Это означает, что функциональные компоненты также могут использоваться внутриstate
.
Foo.js
import React, { useState } from 'react';
function Foo() {
// 声明一个名为“count”的新状态变量
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
export default Foo;
оптимизация
Создание начального состояния стоит дорого, поэтому мы можем использоватьuseState
При передаче функции в API можно избежать повторного создания игнорируемого начального состояния.
Обычный способ:
// 直接传入一个值,在每次 render 时都会执行 createRows 函数获取返回值
const [rows, setRows] = useState(createRows(props.count));
Оптимизированный способ (рекомендуется):
// createRows 只会被执行一次
const [rows, setRows] = useState(() => createRows(props.count));
2. использоватьЭффект
Многие предыдущие операции с побочными эффектами, такие как сетевые запросы, модификация пользовательского интерфейса и т. д., обычно выполняются вclass
компонентcomponentDidMount
илиcomponentDidUpdate
действовать в жизненном цикле. В функциональных компонентах нет понятия этих жизненных циклов, толькоreturn
Элемент, который вы хотите визуализировать.
Но теперь есть место для выполнения побочных эффектов в функциональных компонентах, а именно использованиеuseEffect
функция.
грамматика
useEffect(() => { doSomething });
Два параметра:
-
Первая — это функция, являющаяся побочным эффектом первого рендеринга и последующих рендеров обновления.
- Эта функция может иметь возвращаемое значение, если есть возвращаемое значение, то возвращаемое значение также должно быть функцией, которая будет выполняться при уничтожении компонента.
-
Второй параметр является необязательным и представляет собой массив некоторых свойств побочных эффектов, используемых в первой функции. оптимизировать
useEffect
- Если вы используете эту оптимизацию, убедитесь, что массив содержит изменяющиеся во времени и
effect
любое используемое значение. В противном случае ваш код будет ссылаться на старое значение из предыдущего рендеринга. - Если вы хотите запустить
effect
и очистить его только один раз (при загрузке и выгрузке), вы можете передать пустой массив ([]) в качестве второго параметра. это говоритReact
твойeffect
не зависит отprops
илиstate
любое значение, поэтому его никогда не нужно перезапускать.
- Если вы используете эту оптимизацию, убедитесь, что массив содержит изменяющиеся во времени и
При прохождении [] ближе к знакомому
componentDidMount
иcomponentWillUnmount
Соблюдайте правила, но мы не рекомендуем превращать это в привычку, так как это часто приводит к ошибкам.
использовать контраст
Если у нас есть требование в это время, пустьdocument
изtitle
иFoo
в компонентеcount
Количество раз остается прежним.
Используйте компоненты класса:
Foo.js
import React, { Component } from 'react';
export default class Foo extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
document.title = `You clicked ${ this.state.count } times`;
}
componentDidUpdate() {
document.title = `You clicked ${ this.state.count } times`;
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
И теперь вы также можете выполнять побочные эффекты в функциональных компонентах.
Foo.js
import React, { useState, useEffect } from 'react';
function Foo() {
// 声明一个名为“count”的新状态变量
const [count, setCount] = useState(0);
// 类似于 componentDidMount 和 componentDidUpdate:
useEffect(() => {
// 使用浏览器API更新文档标题
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
export default Foo;
Мало того, мы можем использовать useEffect для выполнения нескольких побочных эффектов (несколько побочных эффектов могут быть выполнены с одним useEffect или по отдельности).
useEffect(() => {
// 使用浏览器API更新文档标题
document.title = `You clicked ${count} times`;
});
const handleClick = () => {
console.log('鼠标点击');
}
useEffect(() => {
// 给 window 绑定点击事件
window.addEventListener('click', handleClick);
});
Кажется, теперь он функционален. Но при использовании компонентов класса мы обычно
componentWillMount
В жизненном цикле выполняются такие операции, как удаление зарегистрированных событий. Итак, как это сделать в функциональных компонентах?
useEffect(() => {
// 使用浏览器API更新文档标题
document.title = `You clicked ${count} times`;
});
const handleClick = () => {
console.log('鼠标点击');
}
useEffect(() => {
// 给 window 绑定点击事件
window.addEventListener('click', handleClick);
return () => {
// 给 window 移除点击事件
window.removeEventListener('click', handleClick);
}
});
Как видите, первый параметр, который мы передаем, может бытьreturn
Функция гаснет,Эта функция автоматически выполняется при уничтожении компонента.
Оптимизация использованияЭффект
Выше мы использовалиuseEffect
Первый параметр в , передается функция. ТакuseEffect
А как насчет второго параметра ?
useEffect
Второй параметр — это массив, который помещается вuseEffect
использовалstate
значение, можно использовать в качестве оптимизации, только если массивstate
Это будет выполняться только при изменении значенияuseEffect
.
useEffect(() => {
// 使用浏览器API更新文档标题
document.title = `You clicked ${count} times`;
}, [ count ]);
Подсказка: Если вы хотите имитировать поведение компонентов класса и выполнять побочные эффекты только тогда, когда componetDidMount, а не componentDidUpdate, тогда
useEffect
Вы можете передать [] в качестве второго параметра. (Но делать это не рекомендуется, из-за пропусков могут возникнуть ошибки)
4. API других хуков
1. использовать контекст
грамматика
const value = useContext(MyContext);
принимает объект контекста (из которогоReact.createContext
возвращаемое значение) и возвращает текущее значение контекста для этого контекста. Текущее значение контекста устанавливается над вызывающим компонентом в деревеvalue
недавнийprop
Конечно<MyContext.Provider>
.
useContext(MyContext)
эквивалентноstatic contextType = MyContext
в классе или<MyContext.Consumer>
.
использование
существуетApp.js
файл для созданияcontext
, и воляcontext
Перейти кFoo
Подсборка
App.js
import React, { createContext } from 'react';
import Foo from './Foo';
import './App.css';
export const ThemeContext = createContext(null);
export default () => {
return (
<ThemeContext.Provider value="light">
<Foo />
</ThemeContext.Provider>
)
}
существуетFoo
компонент, использованиеuseContext
API может получать входящиеcontext
ценность
Foo.js
import React, { useContext } from 'react';
import { ThemeContext } from './App';
export default () => {
const context = useContext(ThemeContext);
return (
<div>Foo 组件:当前 theme 是:{ context }</div>
)
}
Меры предосторожности
useContext
Должен быть параметром самого объекта контекста:
- правильный:
useContext(MyContext)
- Неправильно:
useContext(MyContext.Consumer)
- Неправильно:
useContext(MyContext.Provider)
useContext(MyContext) просто позволяет вам читать контекст и подписываться на его изменения. Вам по-прежнему нужно использовать приведенный выше
в дереве, чтобы предоставить значения для этого контекста.
2. использовать Редуктор
грамматика
const [state, dispatch] = useReducer(reducer, initialArg, init);
useState
альтернатива. Принять тип(state, action) => newState 的reducer
И вернуться кdispatch
Текущее состояние сопряжения методов.
Когда у вас есть сложности, связанные с несколькими подзначениями
state
(состояние) логика, когдаuseReducer
обычно лучше, чемuseState
.
использование
Foo.js
import React, { useReducer } from 'react';
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
export default () => {
// 使用 useReducer 函数创建状态 state 以及更新状态的 dispatch 函数
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<br />
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}
Оптимизация: ленивая инициализация
Начальное состояние также может быть создано лениво. Для этого вы можете передать функцию инициализации в качестве третьего параметра. Начальное состояние будет установлено наinit(initialArg)
.
Позволяет извлечь для расчетаreducer
Логика внешнего начального состояния. Это также удобно для последующего сброса состояния в ответ на действие:
Foo.js
import React, { useReducer } from 'react';
function init(initialCount) {
return {count: initialCount};
}
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
case 'reset':
return init(action.payload);
default:
throw new Error();
}
}
export default ({initialCount = 0}) => {
const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<>
Count: {state.count}
<br />
<button
onClick={() => dispatch({type: 'reset', payload: initialCount})}>
Reset
</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}
Отличие от useState
- когда
state
Когда структура значения состояния более сложная, используйтеuseReducer
имеют больше преимуществ. - использовать
useState
приобретенныйsetState
метод асинхронен при обновлении данных, при использованииuseReducer
приобретенныйdispatch
Данные обновления метода являются синхронными.
Для второго отличия мы можем продемонстрировать:
наверхуuseState
В примере использования мы добавляемbutton
:
useState
серединаFoo.js
import React, { useState } from 'react';
function Foo() {
// 声明一个名为“count”的新状态变量
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
<button onClick={() => {
setCount(count + 1);
setCount(count + 1);
}}>
测试能否连加两次
</button>
</div>
);
}
export default Foo;
нажмитеПроверьте, можете ли вы добавить его дваждыкнопку, вы найдете, нажмите один раз,
count
Все еще только увеличено на 1, видно, что,useState
верноасинхронныйобновить данные;
наверхуuseReducer
В примере использования мы добавляемbutton
:useReducer
серединаFoo.js
import React, { useReducer } from 'react';
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
export default () => {
// 使用 useReducer 函数创建状态 state 以及更新状态的 dispatch 函数
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<br />
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => {
dispatch({type: 'increment'});
dispatch({type: 'increment'});
}}>
测试能否连加两次
</button>
</>
);
}
нажмитеПроверьте, можете ли вы добавить его дваждыкнопку, вы обнаружите, что когда вы щелкаете один раз, количество увеличивается на 2. Можно видеть, что каждый раз, когда отправляется действие, данные будут обновляться один раз, и useReducer действительноСинхронизироватьобновить данные;
3. использоватьОбратный звонок
грамматика
const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]);
возвращаемое значениеmemoizedCallback
Являетсяmemoized
Перезвони. Передайте встроенный обратный вызов и список зависимостей.useCallback
Возвращает запомненную версию отзыва, которая изменяется только в случае изменения одной из зависимостей.
При передаче обратных вызовов оптимизированным дочерним компонентам, которые полагаются на равенство ссылок для предотвращения ненужного рендеринга (например,shouldComponentUpdate
), это очень полезно.
Использование с подкомпонентамиPureComponent
,memo
, вы можете сократить ненужное время рендеринга дочерних компонентов
использование
-
Не используй
useCallback
В случае передачи функций дочерним компонентамFoo.js
import React from 'react'; const Foo = ({ onClick }) => { console.log('Foo:', 'render'); return <button onClick={onClick}>Foo 组件按钮</button> } export default Foo
Bar.js
import React from 'react'; const Bar = ({ onClick }) => { console.log('Bar:', 'render'); return <button onClick={onClick}>Bar 组件按钮</button>; }; export default Bar;
App.js
import React, { useState } from 'react'; import Foo from './Foo'; import Bar from './Bar'; function App() { const [count, setCount] = useState(0); const fooClick = () => { console.log('点击了 Foo 组件的按钮'); }; const barClick = () => { console.log('点击了 Bar 组件的按钮'); }; return ( <div style={{ padding: 50 }}> <p>{count}</p> <Foo onClick={fooClick} /> <br /> <br /> <Bar onClick={barClick} /> <br /> <br /> <button onClick={() => setCount(count + 1)}>count increment</button> </div> ); } export default App;
На этом этапе, когда мы нажмем любую из кнопок увеличения счетчика выше, мы увидим два вывода, напечатанных на консоли, и оба компонента Foo и Bar будут повторно визуализированы. Но на самом деле в нашей текущей логике компоненты Foo и Bar вообще не нужно перерисовывать.
Теперь мы используем
useCallback
оптимизировать -
использовать
useCallback
оптимизированная версияFoo.js
import React from 'react'; const Foo = ({ onClick }) => { console.log('Foo:', 'render'); return <button onClick={onClick}>Foo 组件按钮</button>; }; export default React.memo(Foo);
Bar.js
import React from 'react'; const Bar = ({ onClick }) => { console.log('Bar:', 'render'); return <button onClick={onClick}>Bar 组件按钮</button>; }; export default React.memo(Bar);
App.js
import React, { useCallback, useState } from 'react'; import Foo from './Foo'; import Bar from './Bar'; function App() { const [count, setCount] = useState(0); const fooClick = useCallback(() => { console.log('点击了 Foo 组件的按钮'); }, []); const barClick = useCallback(() => { console.log('点击了 Bar 组件的按钮'); }, []); return ( <div style={{ padding: 50 }}> <p>{count}</p> <Foo onClick={fooClick} /> <br /> <br /> <Bar onClick={barClick} /> <br /> <br /> <button onClick={() => setCount(count + 1)}>count increment</button> </div> ); } export default App;
В этот момент нажмите кнопку увеличения счетчика, и вы увидите, что в консоли нет вывода.
если
useCallback
илиReact.memo
Удалите, вы увидите, что соответствующие компоненты снова окажутся ненужными.render
4. использовать памятку
грамматика
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Возвращает запомненное значение.
Передайте функцию «создать» и массив зависимостей.useMemo
будет пересчитан только при изменении одной из зависимостейmemoized
ценность. Эта оптимизация помогает избежать дорогостоящих вычислений при каждом рендеринге.
Функция, переданная в useMemo во время рендеринга, будет запущена. Не делайте того, чего вы обычно не делаете при рендеринге. Например, побочные эффекты относятся к useEffect, а не к useMemo.
использование
- Данные могут быть кэшированы, подобно
Vue
изcomputed
, который может быть автоматически пересчитан на основе изменений зависимостей - Может помочь нам оптимизировать отрисовку дочерних компонентов, таких как эта сцена:
В компоненте приложения есть два подкомпонента Foo и Bar, когда компонент приложения передается компоненту Foo.
props
Когда происходит изменение, состояние компонента приложения изменяется и перерисовывается. В этот момент компонент Foo и компонент Bar также будут повторно визуализированы. На самом деле эта ситуация пустая трата ресурсов, теперь мы можем использоватьuseMemo
Для оптимизации компонент Foo используетprops
При изменении работает только компонент Foorender
, в то время как Bar не перерисовывается.
пример:
Foo.js
import React from 'react';
export default ({ text }) => {
console.log('Foo:', 'render');
return <div>Foo 组件:{ text }</div>
}
Bar.js
import React from 'react';
export default ({ text }) => {
console.log('Bar:', 'render');
return <div>Bar 组件:{ text }</div>
}
App.js
import React, { useState } from 'react';
import Foo from './Foo';
import Bar from './Bar';
export default () => {
const [a, setA] = useState('foo');
const [b, setB] = useState('bar');
return (
<div>
<Foo text={ a } />
<Bar text={ b } />
<br />
<button onClick={ () => setA('修改后的 Foo') }>修改传给 Foo 的属性</button>
<button onClick={ () => setB('修改后的 Bar') }>修改传给 Bar 的属性</button>
</div>
)
}
На этом этапе, когда мы нажмем любую из вышеуказанных кнопок, мы увидим два вывода, выведенные на консоль, и компоненты A и B будут повторно визуализированы.
Теперь мы используемuseMemo
оптимизировать
App.js
import React, { useState, useMemo } from 'react';
import Foo from './Foo';
import Bar from './Bar';
import './App.css';
export default () => {
const [a, setA] = useState('foo');
const [b, setB] = useState('bar');
+ const foo = useMemo(() => <Foo text={ a } />, [a]);
+ const bar = useMemo(() => <Bar text={ b } />, [b]);
return (
<div>
+ {/* <Foo text={ a } />
+ <Bar text={ b } /> */}
+ { foo }
+ { bar }
<br />
<button onClick={ () => setA('修改后的 Foo') }>修改传给 Foo 的属性</button>
<button onClick={ () => setB('修改后的 Bar') }>修改传给 Bar 的属性</button>
</div>
)
}
На этом этапе, когда мы нажимаем разные кнопки, консоль будет печатать только один вывод, и если a или b изменены, только один из компонентов A и B будет повторно визуализирован.
useCallback(fn, deps) эквивалентно useMemo(() => fn, deps)
5. использоватьСсылка
грамматика
const refContainer = useRef(initialValue);
useRef
вернуть изменяемыйref
объект, который.current
Свойства инициализируются переданным параметром (initialValue). Возвращенный объект будет сохраняться в течение всего времени существования компонента.
- По сути,
useRef
как "коробка", в которой.current
Значение переменной сохраняется в свойстве. -
useRef Hooks
Не только для ссылок DOM. Объект "ref" является общим контейнером,current
Свойства являются изменяемыми и могут содержать любое значение (это могут быть элементы, объекты, примитивные типы или даже функции), аналогично свойствам экземпляров в классах. -
useRef
Имеет способность проникать через затворы
Примечание: useRef() более полезен, чем атрибут ref. Подобно тому, как поля экземпляра используются в классах, удобно хранить любое изменяемое значение.
Обратите внимание, что useRef не будет уведомлять вас об изменении содержимого. Изменение свойства .current не приведет к повторному рендерингу. Если вы хотите запустить некоторый код, когда React прикрепляет или отсоединяет ссылку на узел DOM, вы можете использовать ссылку обратного вызова.
использование
Следующий пример показывает, чтоuseRef()
Сгенерированоref
изcurrent
Хранить элементы, строки в
Example.js
import React, { useRef, useState, useEffect } from 'react';
export default () => {
// 使用 useRef 创建 inputEl
const inputEl = useRef(null);
const [text, updateText] = useState('');
// 使用 useRef 创建 textRef
const textRef = useRef();
useEffect(() => {
// 将 text 值存入 textRef.current 中
textRef.current = text;
console.log('textRef.current:', textRef.current);
});
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.value = "Hello, useRef";
};
return (
<>
{/* 保存 input 的 ref 到 inputEl */}
<input ref={ inputEl } type="text" />
<button onClick={ onButtonClick }>在 input 上展示文字</button>
<br />
<br />
<input value={text} onChange={e => updateText(e.target.value)} />
</>
);
}
нажмитеОтображать текст при вводекнопку, вы можете увидеть, как первый вход появляется наHello, useRef
; Введите содержимое во второй ввод, и вы увидите, что консоль выводит соответствующее содержимое.
6. используйте эффект макета
грамматика
useLayoutEffect(() => { doSomething });
иuseEffect Hooks
Точно так же оба выполняют операции с побочными эффектами. Но он срабатывает после завершения всех обновлений DOM. Может использоваться для выполнения некоторых побочных эффектов, связанных с макетом, таких как получение ширины и высоты элемента DOM, расстояния прокрутки окна и т. д.
Старайтесь отдавать предпочтение useEffect при выполнении побочных эффектов, чтобы не блокировать визуальные обновления. Для независимых от DOM побочных эффектов используйте
useEffect
.
использование
использование сuseEffect
похожий. но будетuseEffect
выполнить до
Foo.js
import React, { useRef, useState, useLayoutEffect } from 'react';
export default () => {
const divRef = useRef(null);
const [height, setHeight] = useState(100);
useLayoutEffect(() => {
// DOM 更新完成后打印出 div 的高度
console.log('useLayoutEffect: ', divRef.current.clientHeight);
})
return <>
<div ref={ divRef } style={{ background: 'red', height: height }}>Hello</div>
<button onClick={ () => setHeight(height + 50) }>改变 div 高度</button>
</>
}
7. использовать императивхендл
В функциональном компоненте нет экземпляра компонента, поэтому вы не можете вызывать состояние или метод в подкомпоненте, связывая экземпляр подкомпонента, как в компоненте класса.
Итак, в функциональном компоненте, как вызвать состояние или метод дочернего компонента в родительском компоненте? Ответ заключается в использованииuseImperativeHandle
грамматика
useImperativeHandle(ref, createHandle, [deps])
-
Первый параметр
ref
Значение, которое можно передать через атрибуты или сопоставить сforwardRef
использовать -
Второй параметр — это функция, которая возвращает объект, и свойства объекта будут подключены к первому параметру.
ref
изcurrent
атрибут -
Третий параметр — это набор зависимых элементов, такой же, как
useEffect
,useCallback
,useMemo
, при изменении зависимости второй параметр будет повторно выполнен и повторно смонтирован в первый параметрcurrent
атрибут
использование
Уведомление:
- Третий параметр зависимость должна быть заполнена по мере необходимости, если меньше, то это приведет к тому, что возвращаемое свойство объекта будет ненормальным, а если слишком много, то вызовет
createHandle
Повторение - компонент или
hook
, для того жеref
, можно использовать только один разuseImperativeHandle
, если он повторяется много раз, он будет выполнен позжеuseImperativeHandle
изcreateHandle
Возвращаемое значение заменит ранее выполненноеuseImperativeHandle
изcreateHandle
возвращаемое значение
Foo.js
import React, { useState, useImperativeHandle, useCallback } from 'react';
const Foo = ({ actionRef }) => {
const [value, setValue] = useState('');
/**
* 随机修改 value 值的函数
*/
const randomValue = useCallback(() => {
setValue(Math.round(Math.random() * 100) + '');
}, []);
/**
* 提交函数
*/
const submit = useCallback(() => {
if (value) {
alert(`提交成功,用户名为:${value}`);
} else {
alert('请输入用户名!');
}
}, [value]);
useImperativeHandle(
actionRef,
() => {
return {
randomValue,
submit,
};
},
[randomValue, submit]
);
/* !! 返回多个属性要按照上面这种写法,不能像下面这样使用多个 useImperativeHandle
useImperativeHandle(actionRef, () => {
return {
submit,
}
}, [submit])
useImperativeHandle(actionRef, () => {
return {
randomValue
}
}, [randomValue])
*/
return (
<div className="box">
<h2>函数组件</h2>
<section>
<label>用户名:</label>
<input
value={value}
placeholder="请输入用户名"
onChange={e => setValue(e.target.value)}
/>
</section>
<br />
</div>
);
};
export default Foo;
App.js
import React, { useRef } from 'react';
import Foo from './Foo'
const App = () => {
const childRef = useRef();
return (
<div>
<Foo actionRef={childRef} />
<button onClick={() => childRef.current.submit()}>调用子组件的提交函数</button>
<br />
<br />
<button onClick={() => childRef.current.randomValue()}>
随机修改子组件的 input 值
</button>
</div>
);
};
5. Попробуйте написать собственные хуки
Здесь мы подражаем официальномуuseReducer
сделать обычайHooks
.
1. Напишите собственный useReducer
существуетsrc
Создайте новый в каталогеuseReducer.js
документ:
useReducer.js
import React, { useState } from 'react';
function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState);
function dispatch(action) {
const nextState = reducer(state, action);
setState(nextState);
}
return [state, dispatch];
}
Совет: Хуки можно использовать не только в функциональных компонентах, но и в других хуках.
2. Используйте пользовательский useReducer
ОК, обычайuseReducer
Написание завершено, давайте посмотрим, можно ли его использовать нормально?
переписатьFoo
компоненты
Example.js
import React from 'react';
// 从自定义 useReducer 中引入
import useReducer from './useReducer';
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
export default () => {
// 使用 useReducer 函数创建状态 state 以及更新状态的 dispatch 函数
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<br />
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}
5. Использование хуков и написание спецификаций
-
Не начинайте с обычного
JavaScript
вызов функцииHooks
; -
Не вызывайте циклы, условные операторы или вложенные функции.
Hooks
; -
Должен вызываться на верхнем уровне компонента
Hooks
; -
Доступна с
React
вызов функционального компонентаHooks
; -
можно настроить из
Hooks
вызыватьHooks
; -
настроить
Hooks
должен использоватьuse
Вначале это условность;
6. Используйте плагин ESLint, предоставленный React
Согласно предыдущему абзацу, вReact
используется вHooks
Необходимо соблюдать некоторые определенные правила. Однако в процессе написания кода эти правила использования могут игнорироваться, что приводит к неконтролируемым ошибкам. В этом случае мы можем использовать плагин ESLint, предоставленный React:eslint-plugin-react-hooks. Давайте посмотрим, как его использовать.
Установите плагин ESLint.
$ npm install eslint-plugin-react-hooks --save
Использование плагинов в .eslintrc
// Your ESLint configuration
// "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
// "react-hooks/exhaustive-deps": "warn" // Checks effect dependencies
{
"plugins": [
"react-hooks"
],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}
7. Справочные документы
Реагировать на официальный сайт
написать на обороте
Если в письме есть что-то неправильное или неточное, вы можете высказать свое ценное мнение, большое спасибо.
Если вам нравится или помогаете, добро пожаловать в Star, что также является поощрением и поддержкой для автора.