Подробное объяснение использования React Hooks

React.js

В этой статье дается подробное объяснение новых функций хуков, выпущенных 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компонент, использованиеuseContextAPI может получать входящие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>
            &nbsp;&nbsp;&nbsp;&nbsp;
            <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>
            &nbsp;&nbsp;&nbsp;&nbsp;
            <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. Справочные документы

Реагировать на официальный сайт

React Hooks FAQ

написать на обороте

Если в письме есть что-то неправильное или неточное, вы можете высказать свое ценное мнение, большое спасибо.

Если вам нравится или помогаете, добро пожаловать в Star, что также является поощрением и поддержкой для автора.