Четырехмерное резюме реагирующих хуков поможет вам укрепить основу

внешний интерфейс React.js
Четырехмерное резюме реагирующих хуков поможет вам укрепить основу

предисловие

Я читал и читал много статей о крючках на Наггетс, но чувствую, что они не очень подробные. А также много гидрологии. В последнее время я планирую снова научиться реагировать и систематически снова изучать хуки.

Крючки что ли?

  • react-hooks — это новый API-интерфейс для реакции после react16.8, который позволяет вам использовать состояние и другие функции React без написания классов.
  • Хуки — это функции, которые позволяют вам «подключаться» к функциям состояния и жизненного цикла React в функциональных компонентах.

Зачем использовать хуки?

Недостатки классовых компонентов: (отМотивация сайта)

  • Повторное использование логики состояния между компонентами сложно

  • Сложные компоненты становятся трудными для понимания

  • непонятный класс

Вы должны понимать JavaScriptthisработает, что значительно отличается от других языков. И не забудьте привязать обработчики событий. нестабильныйПредложение по грамматике, код очень избыточен.

Появление крючков решает вышеуказанные проблемы. Кроме того, есть и другие преимущества:

  • Увеличьте возможность повторного использования и логику кода, компенсируйте дефект, заключающийся в том, что компоненты без состояния не имеют жизненного цикла и состояния управления данными.
  • Идея реактивных хуков ближе к функциональному программированию. Используйте объявление функции вместо объявления класса.Хотя class также является синтаксическим сахаром конструктора es6, react-hooks записывает функции как компоненты, что, несомненно, повышает эффективность разработки кода (нет необходимости писать цикл объявления и жизненный цикл, как компоненты объявления класса. функция рендеринга и т. д.)

Никаких критических изменений для хуков

  • Совершенно необязательно.Вы можете попробовать хуки в некоторых компонентах, не переписывая существующий код. Но если вы не хотите, вам не нужно изучать или использовать хуки прямо сейчас.
  • 100% обратная совместимость.Хуки не содержат критических изменений.
  • Доступен сейчас.Хук был выпущен в версии 16.8.0.

Правила использования хуков

1. Используйте хуки только на верхнем уровне, не вызывайте хуки в циклах, условных выражениях или вложенных функциях.

Обязательно всегда вызывайте их на верхнем уровне ваших функций React. Следуя этому правилу, вы можете гарантировать, что хуки вызываются в одном и том же порядке при каждом рендеринге. Это позволяет ReactuseState и useEffectПоддерживайте правильное состояние ловушки между вызовами.

2. Вызывайте хуки только в функциях React

Вместо того, чтобы вызывать хуки в обычных функциях JavaScript, вы можете:

  • ✅ Call Hook в функциональном компоненте React
  • ✅ Вызов других хуков в пользовательских хуках

Что касается того, почему существуют эти правила, если вы заинтересованы, пожалуйста, обратитесь кПравила хука

useState

const [state, setState] = useState(initialState)

  • useState имеет параметр (initialState может быть функцией, возвращающей значение, но обычно не используется), этот параметр может иметь любой тип данных и обычно используется как значение по умолчанию.
  • Возвращаемое значение useState — это массив, первый параметр массива — это состояние, которое нам нужно использовать, а второй параметр — это функция для изменения состояния (функция аналогична this.setState)

Взгляните на случай таймера

import React,{useState} from "react";
function Example() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}
export default Example;
  • Первая строка:Представлено в ReactuseStateКрюк. Это позволяет нам хранить внутреннее состояние в функциональных компонентах.
  • Третий ряд: существует ExampleВнутри компонента мы вызываемuseStateХук объявляет новую переменную состояния. Он возвращает пару значений нашей именованной переменной. Мы называем переменную какcount, потому что он хранит клики. Мы проходим0в видеuseStateединственный параметр для его инициализации как0. Второе возвращаемое значение само по себе является функцией. это позволяет нам обновлятьcountзначение , поэтому мы называем этоsetCount.
  • Седьмая строка:Когда пользователь нажимает кнопку, мы передаем новое значение вsetCount. React будет повторно отображатьExampleкомпоненты, и поставить последниеcountперейти к нему.

Использование нескольких переменных состояния

 // 声明多个 state 变量
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: '学习 Hook' }]);

тыне нужноИспользуйте несколько переменных состояния. Переменные состояния отлично подходят для хранения объектов и массивов, поэтому вы можете группировать связанные данные вместе.

Обновить состояние

import React,{useState} from "react";
function Example() {
  const [count, setCount] = useState(0);
  const [person, setPerson] = useState({name:'jimmy',age:22});
  return (
    <div>
      <p>name {person.name} </p>
      // 如果新的 state 需要通过使用先前的 state 计算得出,那么可以将回调函数当做参数传递给 setState。
      // 该回调函数将接收先前的 state,并返回一个更新后的值。
      <button onClick={() => setCount(count=>count+1)}>Click me</button>
      <button onClick={() => setPerson({name:'chimmy'})}>Click me</button>
    </div>
  );
}
export default Example;

Когда setPerson обновляет человека, в отличие от классаthis.setState, всегда обновляя переменную состояниязаменятьэто вместо слияния его. Человек в приведенном выше примере {name:'chimmy'} вместо {name:'chimmy',age:22}

useEffect

Effect HookПозволяет выполнять побочные эффекты (извлечение данных, настройка подписок и ручное изменение DOM в компонентах React — все это побочные эффекты) в функциональных компонентах.

useEffect(fn, array)

useEffect выполняется один раз после первоначального рендеринга, со вторым параметром для имитации некоторых жизненных циклов класса.

Если вы знакомы с функциями жизненного цикла класса React, вы можете поместитьuseEffectКрюк какcomponentDidMount``componentDidUpdate и componentWillUnmountСочетание этих трех функций.

useEffect реализует компонентDidMount

Если второй параметр является пустым массивом, useEffect эквивалентен componentDidMount в компоненте класса.

import React, { useState, useEffect } from "react";
function Example() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    console.log("我只会在组件初次挂载完成后执行");
  }, []);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}
export default Example;

После рендеринга страницы useEffect выполняется один раз. Вывести "Я выполню только после того, как компонент смонтируется в первый раз" При изменении состояния по нажатию кнопки и повторном рендеринге страницы useEffect выполняться не будет.

useEffect реализует компонентDidUpdate

Если вы не передадите второй параметр, useEffect будет выполняться при первом рендеринге и каждом обновлении.

import React, { useState, useEffect } from "react";
function Example() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    console.log("我会在初次组件挂载完成后以及重新渲染时执行");
  });
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}
export default Example;

При первом рендеринге useEffect будет выполнен один раз и выведет «Я выполню его после завершения начального монтирования компонента и повторного рендеринга». При нажатии кнопки состояние изменяется, страница перерисовывается, выполняется useEffect и «Я буду выполняться после завершения начального монтирования компонента и его повторного рендеринга».

useEffect реализует компонентWillUnmount

Эффект возвращает функцию, которую React вызовет при выполнении операции очистки.

useEffect(() => {
    console.log("订阅一些事件");
    return () => {
      console.log("执行清除操作")
    }
  },[]);

Примечание. «Выполненная очистка» печатается не только при уничтожении компонента, но и при каждом повторном рендеринге. Что касается причины, я думаю, что официальный сайт объясняет это очень четко, пожалуйста, обратитесь к объяснению:Почему вам нужно запускать Эффект каждый раз, когда вы обновляете

Контролировать выполнение useEffect

import React, { useState, useEffect } from "react";
function Example() {
  const [count, setCount] = useState(0);
  const [number, setNumber] = useState(1);
  useEffect(() => {
    console.log("我只会在cout变化时执行");
  }, [count]);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click cout</button>
      <button onClick={() => setNumber(number + 1)}>Click number</button>
    </div>
  );
}
export default Example;

В приведенном выше примере при нажатии кнопки cout будет напечатано «Я буду выполнять только при изменении cout». Поскольку зависимостью в массиве второго параметра useEffect является cout, useEffect будет выполняться только при изменении cout. Если в массиве несколько элементов, React выполнит эффект, даже если изменится только один элемент.

Разделение проблем с использованием нескольких эффектов

Используйте один из крючковЦельИменно для решения проблемы функции жизненного цикла в классе часто содержат несвязанную логику, но связанная логика разделена на несколько разных методов.

import React, { useState, useEffect } from "react";
function Example() {
  useEffect(() => {
    // 逻辑一
  });
  useEffect(() => {
    // 逻辑二
  });
   useEffect(() => {
    // 逻辑三
  });
  return (
    <div>
      useEffect的使用
    </div>
  );
}
export default Example;

Хуки позволяют нам разделить код по назначению,вместо функции жизненного цикла. React будет вызывать компоненты в том порядке, в котором объявлены эффекты.Каждыйэффект.

Использование асинхронных функций в useEffect

useEffect не может напрямую использовать async await Синтаксический сахар

/* 错误用法 ,effect不支持直接 async await*/
 useEffect(async ()=>{
        /* 请求数据 */
      const res = await getData()
 },[])

useEffectПараметр обратного вызова возвращает функцию, которая устраняет побочные эффекты.clean-upфункция. так не могу вернутьсяPromise, и нельзя использоватьasync/await

Итак, как мы должны сделатьuseEffectслужба поддержкиasync/awaitШерстяная ткань?

Способ 1 (рекомендуется)

const App = () => {
  useEffect(() => {
    (async function getDatas() {
      await getData();
    })();
  }, []);
  return <div></div>;
};

Способ второй

  useEffect(() => {
    const getDatas = async () => {
      const data = await getData();
      setData(data);
    };
    getDatas();
  }, []);

что делает useEffect

Используя этот хук, вы можете сообщить компонентам React, что渲染后выполнить какое-либо действие. React сохраняет переданную вами функцию (назовем ее «эффектом») и вызывает ее после выполнения обновления DOM.

Зачем вызывать внутри компонентаuseEffect?

будет useEffectПомещение его внутрь компонента позволяет нам напрямую обращаться к нему в эффекте.countпеременные состояния (или другие реквизиты). Нам не нужен специальный API для его чтения — он уже хранится в области видимости функции. Хуки используют механизм закрытия JavaScript вместо того, чтобы вводить определенные API React, когда JavaScript уже предоставляет решение.

useContext

концепция

const value = useContext(MyContext);

получает объект контекста (React.createContextвозвращаемое значение ) и возвращает текущее значение этого контекста. Когда верхний слой компонента является ближайшим<MyContext.Provider>При обновлении этот хук вызовет повторный рендеринг и использует последний переданныйMyContextконтекст провайдераvalueценность. даже если предки используютReact.memo или shouldComponentUpdate, также используется в самом компонентеuseContextпри повторном рендеринге.

Не забудьте useContextПараметр должен бытьсам объект контекста:

  • правильный:  useContext(MyContext)
  • Ошибка:  useContext(MyContext.Consumer)
  • Ошибка:  useContext(MyContext.Provider)

Пример

index.js

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
// 创建两个context
export const UserContext = React.createContext();
export const TokenContext = React.createContext();
ReactDOM.render(
  <UserContext.Provider value={{ id: 1, name: "chimmy", age: "20" }}>
    <TokenContext.Provider value="我是token">
      <App />
    </TokenContext.Provider>
  </UserContext.Provider>,
  document.getElementById("root")
);

app.js

import React, { useContext } from "react";
import { UserContext, TokenContext } from "./index";

function Example() {
  let user = useContext(UserContext);
  let token = useContext(TokenContext);
  console.log("UserContext", user);
  console.log("TokenContext", token);
  return (
    <div>
      name:{user?.name},age:{user?.age}
    </div>
  );
}
export default Example;

Напечатанное значение выглядит следующим образом

41PXCT[XET_ZJ7]79K@~6JX.png

намекать

Если вы уже знакомы с контекстным API до того, как коснулись хука, это должно быть понятно.useContext(MyContext)Эквивалент в компоненте классаstatic contextType = MyContext или <MyContext.Consumer>.

useContext(MyContext)просто чтобы вы могличитатьЗначение контекста и изменения контекста подписки. Вам все еще нужно использовать в верхнем дереве компонентов<MyContext.Provider>для компонентов нижнего уровняпоставкаконтекст.

useReducer

концепция

const [state, dispatch] = useReducer(reducer, initialArg, init);

useStateальтернатива. Он получает(state, action) => newStateРедуктор и возвращает текущее состояние и его соответствиеdispatchметод. (Если вы знакомы с Redux, вы уже знаете, как он работает.)

В некоторых сценарияхuseReducerбудет лучше, чемuseStateБолее применимо, например, логика состояния более сложна и содержит несколько подзначений, или следующее состояние зависит от предыдущего состояния и т. д. и, используяuseReducerОн также может выполнять оптимизацию производительности для компонентов, запускающих глубокие обновления, поскольку вы можете передавать их дочерним компонентам.dispatchвместо функции обратного вызова

будь осторожен

React позаботится о том, чтобыdispatchИдентификатор функции стабилен и не меняется при повторном рендеринге компонента. Вот почему безопасноuseEffect или useCallbackисключен из списка зависимостейdispatch.

Пример

import React, { useReducer } from "react";
export default function Home() {
  function reducer(state, action) {
    switch (action.type) {
      case "increment":
        return { ...state, counter: state.counter + 1 };
      case "decrement":
        return { ...state, counter: state.counter - 1 };
      default:
        return state;
    }
  }
  const [state, dispatch] = useReducer(reducer, { counter: 0 });
  return (
    <div>
      <h2>Home当前计数: {state.counter}</h2>
      <button onClick={(e) => dispatch({ type: "increment" })}>+1</button>
      <button onClick={(e) => dispatch({ type: "decrement" })}>-1</button>
    </div>
  );
}

useCallback

концепция

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

Возвращает [запоминаемую] функцию обратного вызова.

Передайте встроенную функцию обратного вызова и массив зависимостей в качестве параметровuseCallback, который возвращает запомненную версию функции обратного вызова, которая обновляется только при изменении зависимости. Когда вы передаете обратные вызовы для оптимизации и используете ссылочное равенство, чтобы избежать ненужного рендеринга (например,shouldComponentUpdate), это будет очень полезно.

Пример

import React, { useState } from "react";
// 子组件
function Childs(props) {
  console.log("子组件渲染了");
  return (
    <>
      <button onClick={props.onClick}>改标题</button>
      <h1>{props.name}</h1>
    </>
  );
}
const Child = React.memo(Childs);
function App() {
  const [title, setTitle] = useState("这是一个 title");
  const [subtitle, setSubtitle] = useState("我是一个副标题");
  const callback = () => {
    setTitle("标题改变了");
  };
  return (
    <div className="App">
      <h1>{title}</h1>
      <h2>{subtitle}</h2>
      <button onClick={() => setSubtitle("副标题改变了")}>改副标题</button>
      <Child onClick={callback} name="桃桃" />
    </div>
  );
}

Результат выполнения следующийimage.png

когда я нажимаюизменить субтитрыПосле этой кнопки субтитр изменится на «Субтитры изменены», и консоль снова распечатает子组件渲染了, что доказывает, что дочерний компонент перерисовывается, но в дочернем компоненте нет изменений, поэтому перерисовка дочернего компонента на этот раз избыточна, так как же избежать этого избыточного рендеринга?

найти причину

Прежде чем мы решим проблему,Прежде всего, в чем причина этой проблемы?

Разберем, перерисовывается компонент, вообще бывает три случая:

  1. Либо изменяется собственное состояние компонента
  2. Либо родительский компонент повторно рендерится, вызывая повторный рендеринг дочернего компонента, но реквизиты родительского компонента не изменились
  3. Либо родительский компонент повторно рендерится, вызывая повторный рендеринг дочернего компонента, но реквизиты, переданные родительским компонентом, изменяются.

Затем используйте метод исключения, чтобы узнать, что вызвало это:

Первый явно исключается, когда щелчокизменить субтитрыКогда это не меняет состояние дочернего компонента;

Во втором случае используемReact.memoрешить эту проблему, поэтому эта ситуация также исключена.

Затем есть третий случай.Когда родительский компонент перерисовывается, реквизиты, переданные дочернему компоненту, изменились.Посмотрите на два свойства, переданные дочернему компоненту, одно из нихname,одинonClick,nameпередана константа, она не изменится, какие измененияonClickТеперь, почему функция обратного вызова, переданная в onClick, изменяется? На самом деле, каждый раз, когда функциональный компонент перерисовывается, функциональный компонент будет повторно выполняться с самого начала, поэтому функция обратного вызова, созданная эти два раза, должна была измениться, что вызвало повторный рендеринг подкомпонента.

Решаем проблему с помощью useCallback

const callback = () => {
  doSomething(a, b);
}
const memoizedCallback = useCallback(callback, [a, b])

Передать функции и зависимости в качестве параметровuseCallback, который вернет мемоизированную версию функции обратного вызова, этот memoizedCallback будет обновляться только при изменении зависимостей.

Затем просто измените функцию обратного вызова, переданную дочернему компоненту таким образом, и все будет в порядке.

const callback = () => { setTitle("标题改变了"); };
// 通过 useCallback 进行记忆 callback,并将记忆的 callback 传递给 Child
<Child onClick={useCallback(callback, [])} name="桃桃" />

Таким образом, мы видим, что рендеринг подкомпонента будет напечатан только при первом рендеринге, а рендеринг подкомпонента не будет напечатан при щелчке, чтобы изменить субтитр и изменить заголовок.

useMemo

концепция

const cacheSomething = useMemo(create,deps)

  • create: первый параметр — это функция, а возвращаемое значение функции используется как кэшированное значение.
  • deps: Второй параметр — это массив, в котором хранятся зависимости текущего useMemo. Когда компонент функции будет выполняться в следующий раз, он сравнит состояние в зависимостях deps, чтобы увидеть, есть ли какие-либо изменения. -execute create для получения нового кэшированного значения.
  • cacheSomething: возвращаемое значение, возвращаемое значение выполнения create. Если есть изменение зависимости в deps, возвращаемое значение является значением, сгенерированным повторным выполнением create, в противном случае берется последнее кэшированное значение.

Принцип использования Памятка

useMemo запишет возвращаемое значение последнего выполнения create и привяжет его к объекту волокна, соответствующему функциональному компоненту.Пока компонент не уничтожен, кэшированное значение всегда будет существовать, но если есть изменение в deps, create будет выполнен повторно.Возвращенное значение записывается в объект волокна как новое значение.

Пример

function Child(){
    console.log("子组件渲染了")
    return <div>Child</div> 
}
const Child = memo(Child)
function APP(){
    const [count, setCount] = useState(0);
    const userInfo = {
      age: count,
      name: 'jimmy'
    }
    return <Child userInfo={userInfo}>
}

Когда функциональный компонент перерисовывается, userInfo каждый раз будет новым объектом, независимо отcountНезависимо от того, есть ли изменение или нет, оно вызовет повторную визуализацию дочернего компонента.

И следующее будет вcountНовый объект возвращается только после изменения.

function Child(){
    console.log("子组件渲染了")
    return <div>Child</div> 
}
function APP(){
    const [count, setCount] = useState(0);
    const userInfo = useMemo(() => {
      return {
        name: "jimmy",
        age: count
      };
    }, [count]);
    return <Child userInfo={userInfo}>
}

На самом деле, роль useMemo больше, чем это Согласно введению в официальном документе: поместить в useMemo некоторую дорогостоящую логику вычислений и обновлять ее только при изменении зависимого значения.

import React, {useState, useMemo} from 'react';

// 计算和的函数,开销较大
function calcNumber(count) {
  console.log("calcNumber重新计算");
  let total = 0;
  for (let i = 1; i <= count; i++) {
    total += i;
  }
  return total;
}
export default function MemoHookDemo01() {
  const [count, setCount] = useState(100000);
  const [show, setShow] = useState(true);
  const total = useMemo(() => {
    return calcNumber(count);
  }, [count]);
  return (
    <div>
      <h2>计算数字的和: {total}</h2>
      <button onClick={e => setCount(count + 1)}>+1</button>
      <button onClick={e => setShow(!show)}>show切换</button>
    </div>
  )
}

Когда мы переходим к нажатию кнопки переключения показа, функция, которая вычисляет сумму calcNumber, не отображается, только когда количество изменяется, вычисление происходит.

Сводка useCallback и useMemo

Простое понимание useCallback и useMemo состоит в том, что один кэш — это функция, а другой — результат, возвращаемый функцией. useCallback предназначен для оптимизации дочерних компонентов и предотвращения повторного рендеринга дочерних компонентов. useMemo может оптимизировать текущий компонент, а также подкомпоненты.Оптимизация текущего компонента в основном кэширует некоторую сложную вычислительную логику через memoize. Конечно, нет необходимости использовать useMemo, если он просто выполняет простые вычисления.

Мы можем определить возвращаемое значение useMemo как возвращающую функцию, чтобы мы могли гибко реализовать useCallback.useCallback(fn, deps)эквивалентноuseMemo(() => fn, deps).

useRef

const refContainer = useRef(initialValue);

useRefВозвращает изменяемый объект ref, чей.currentСвойство инициализируется переданным параметром (initialValue). Возвращенный объект ref остается неизменным в течение всего времени жизни компонента.

useRef получить дом

useRef, у него есть параметр, который можно использовать в качестве начального значения кэшированных данных, возвращаемое значение может быть помечено ссылкой на элемент dom, и можно получить отмеченный узел элемента.

import React, { useRef } from "react";
function Example() {
  const divRef = useRef();
  function changeDOM() {
    // 获取整个div
    console.log("整个div", divRef.current);
    // 获取div的class
    console.log("div的class", divRef.current.className);
    // 获取div自定义属性
    console.log("div自定义属性", divRef.current.getAttribute("data-clj"));
  }
  return (
    <div>
      <div className="div-class" data-clj="我是div的自定义属性" ref={divRef}>
        我是div
      </div>
      <button onClick={(e) => changeDOM()}>获取DOM</button>
    </div>
  );
}
export default Example;

1.png

Данные кеша useRef

Еще одна важная роль useRef — кэширование данных.Мы знаем, что usestate и useReducer могут сохранять текущий источник данных, но если они обновят выполнение функции источника данных, то это обязательно приведет весь компонент из нового выполнения в рендеринг. объявите переменную внутри, следующее обновление также сбросит ее.Если мы хотим сохранить данные тихо, не запуская обновление функции, то useRef — отличный выбор.

Вот пример изменения последнего значения состояния каждый раз

import React, { useRef, useState, useEffect } from "react";
function Example() {
  const [count, setCount] = useState(0);

  const numRef = useRef(count);

  useEffect(() => {
    numRef.current = count;
  }, [count]);

  return (
    <div>
      <h2>count上一次的值: {numRef.current}</h2>
      <h2>count这一次的值: {count}</h2>
      <button onClick={(e) => setCount(count + 10)}>+10</button>
    </div>
  );
}
export default Example;

Когда содержимое объекта ref изменяется,useRef иНе будусообщаю тебе. изменять.currentСвойство не вызывает повторную визуализацию компонента. Таким образом, в приведенном выше примере, хотя значение numRef.current изменилось, последнее значение по-прежнему отображается на странице, а последнее обновленное значение будет отображаться только при повторном обновлении.

напиши в конце

Если в статье есть ошибки, укажите на них.