Я много работал, чтобы понять хуки реакции в одной статье

React.js

представлять

react hooksдаReact 16.8новые особенности. Это позволяет нам использовать в функциональных компонентахstate, жизненный цикл и многое другоеreactхарактеристики, не ограничиваясьclassкомпоненты

react hooksвнешний видreactНе будет компонентов без состояния, только компоненты класса и компоненты функций.

В сравнении

Существовать значит быть разумным,hooksОн не исключение, его появление говорит о том, что ему нужно решить какие-то проблемы.classДефекты или недостатки компонентов, тогда давайте сначала посмотримclassЧего не хватает или проблема существует в компоненте

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

вопрос решение недостаток
Жизненный цикл раздут и логически связан без
Логику сложно использовать повторно Решено по наследству Множественное наследование не поддерживается
Решено hoc Добавит дополнительное вложение компонентов, а также окажет некоторое влияние на производительность.
свойства рендеринга То же самое, раздутая иерархия, влияние на производительность
class thisуказать на проблему анонимная функция Каждый раз создавать новую функцию, повторяя ненужный рендеринг дочерних компонентов
bind Нужно написать много кода, который не имеет ничего общего с логикой и состоянием

а такжеhooksЕсть лучшие решения этих проблем

  1. никого не осталосьclass,Естественно нетthisуказать на проблему
  2. путем настройкиuseEffectрешить проблему повторного использования
  3. используяuseEffectЧтобы разделить логику и уменьшить сцену раздутой логики

Конечно,hooksЭто палка о двух концах.Если вы используете его хорошо, вы можете добиться эффекта.Если вы используете его плохо, это снизит эффективность и качество разработки.Потом мы посмотрим, как его использовать лучше.hooksБар

конкретное использование

Использование состояния использования

Простой пример

hooksСпособность заключается в том, чтобы позволить нам использовать в функциональных компонентахstateЭто черезuseStateДля достижения рассмотрим простой пример

  function App () {
    const [ count, setCount ] = useState(0)
    return (
      <div>
        点击次数: { count } 
        <button onClick={() => { setCount(count + 1)}}>点我</button>
      </div>
      )
  }

useStateочень прост в использовании, мы начинаем сReactЗалезайuseStateПосле этого вам нужно только звонить прямо туда, где вы его используете.useStateфункция может,useStateвернет массив, первое значение будет нашимstate,Второе значение - это функция, используемая для измененияstate, Тогда почему это называется здесьcountа такжеsetCount? Вы должны называть это, используется здесьes6присваивание деструктурирования, так что вы можете дать ему любое имя,updateCount, doCount,any thing, конечно, ради стандартов кодирования рекомендуется использовать соглашение об именах единообразно, особенно второе значение

то же значение

когда мы используемuseStateКогда одно и то же значение передается при изменении значения, будет ли наш компонент перерисовываться, например, как это

  function App () {
    const [ count, setCount ] = useState(0)
    console.log('component render count')
    return (
      <div>
        点击次数: { count } 
        <button onClick={() => { setCount(count)}}>点我</button>
      </div>
      )
  }

Результата нет, пользуйтесь смело

значение по умолчанию useState

useStateПоддерживает нас, чтобы напрямую передать значение при вызове, чтобы указатьstateзначение по умолчанию, как этоuseState(0), useState({ a: 1 }), useState([ 1, 2 ]), также позволяет нам передать функцию для логического вычисления значения по умолчанию, например эту

function App (props) {
    const [ count, setCount ] = useState(() => {
      return props.count || 0
    })
    return (
      <div>
        点击次数: { count } 
        <button onClick={() => { setCount(count + 1)}}>点我</button>
      </div>
      )
  }

В это время маленький партнер попросил, чтобы каждый раз, когда мой компонент рендерился,useStateБудут ли функции вuseStateФункция in будет выполнена только один раз, мы можем сделать тест

function App (props) {
    const [ count, setCount ] = useState(() => {
      console.log('useState default value function is call')
      return props.count || 0
    })
    return (
      <div>
        点击次数: { count } 
        <button onClick={() => { setCount(count + 1)}}>点我</button>
      </div>
      )
  }

оказаться

Получить значение предыдущего раунда, когда setUseState

мы используемuseStateВторой параметр, мы хотим получитьstateзначение, нужно толькоuseStateВозвращается второй параметр, который используется в нашем примере выше.setCountПри использовании передать параметр, параметром функции является предыдущий раундstateзначение


  setCount((count => count + 1)

Случай нескольких useStates

useStateМы не можем использовать только один, когда мы используем несколькоuseStateкогда этоreactКак определить какой из них какой на самом деле очень просто, он записывается по порядку первого исполнения, что эквивалентно хранению каждого компонентаuseStateгде массив, каждый раз используется новыйuseState, в массивpushОдинuseState, то конечно когда меняем, складываем, вычитаем во время выполненияuseStateчас,reactМожет еще нормально работать?

function App (props) {
  let count, setCount
  let sum, setSum
  if (count > 2) {
    [ count, setCount ] = useState(0)
    [ sum, setSum ] = useState(10)
  } else {
    [ sum, setSum ] = useState(10)
    [ count, setCount ] = useState(0)
  }
  return (
    <div>
      点击次数: { count } 
      总计:{ sum }
      <button onClick={() => { setCount(count + 1); setSum(sum - 1)}}>点我</button>
    </div>
    )
}

когда мы меняем во время выполненияuseStateпорядок, данные будут загромождены, увеличиваяuseState, программа сообщит об ошибке

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

Также рекомендуется использоватьeslint-plugin-react-hooksПлагины для стандартизации написания кода и проверки этой ситуации

useStateИспользование так просто, я узнал, Далее, давайте посмотримuseEffectиспользование

Использование useEffect

Effect HookПозволяет выполнять операции с побочными эффектами в функциональных компонентах. Здесь упоминаются побочные эффекты. Что такое побочные эффекты? Кроме логики, связанной с состоянием, такой как сетевые запросы, прослушивание событий, поискdom

Простой пример

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

  class App extends PureComponent {
    state = {
      count: 0
    }

    componentDidMount() {
      document.title = count
    }

    componentDidUpdate() {
      document.title = count
    }
    render () {
      const { count } = this.state
      return (
        <div>
          页面名称: { count } 
          <button onClick={() => { this.setState({ count: count++ })}}>点我</button>
        </div>
      )
    }
  }

использоватьhooksкак писать

function App () {
  const [ count, setCount ] = useState(0)

  useEffect(() => {
    document.title = count
  })

  return (
    <div>
      页面名称: { count } 
      <button onClick={() => { setCount(count + 1 )}}>点我</button>
    </div>
    )
}

useEffectЧто это такое, давайте сначала проигнорируем его и вернемся к нашему резюмеclassпроблемы с компонентами,useStateПросто позвольте нашему функциональному компоненту использоватьstateспособность, то мы должны решитьclassДля проблем компонентов решим первую, проблему раздутого жизненного цикла.

Жизненный цикл useEffect

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

В прошлом мы привязывали события, отвязывали события, устанавливали таймеры, находилиdomкогда черезcomponentDidMount,componentDidUpdate,componentWillUnmountжизненный цикл для достижения, иuseEffectбудет выполняться в компоненте каждый разrenderПосле вызова это эквивалентно этим трем функциям жизненного цикла, но вы можете решить, следует ли вызывать, передав параметры

Отмечается, что,useEffectвозвращает функцию обратного вызова, которая очищает состояние, оставшееся от последнего побочного эффекта, еслиuseEffectВызывается только один раз, функция обратного вызова эквивалентнаcomponentWillUnmountЖизненный цикл

См. пример ниже

  function App () {
    const [ count, setCount ] = useState(0)
    const [ width, setWidth ] = useState(document.body.clientWidth)

    const onChange = () => {
      
      setWidth(document.body.clientWidth)
    }

    useEffect(() => {
      window.addEventListener('resize', onChange, false)

      return () => {
        window.removeEventListener('resize', onChange, false)
      }
    })

    useEffect(() => {
      document.title = count
    })

    return (
      <div>
        页面名称: { count } 
        页面宽度: { width }
        <button onClick={() => { setCount(count + 1)}}>点我</button>
      </div>
      )
  }

Следуя нашему предыдущему простому примеру, мы должны иметь дело с двумя видами логики побочных эффектов в приведенном выше примере, здесь мы должны иметь дело с обоимиtitle, но и следить за изменением ширины экрана, согласноclassНаписано так: нам приходится иметь дело с этими двумя видами логики в жизненном цикле, но вhooks, нам нужно только дваuseEffectможет решить эти проблемы, как мы упоминали ранее,useEffectМожет вернуть функцию для очистки состояния, оставленного последним побочным эффектом. Мы можем использовать это место, чтобы отвязать прослушиватели событий. В этом месте есть проблема, то естьuseEffectкаждый разrenderЗатем он будет вызываться, например.titleизменение, эквивалентноеcomponentDidUpdate, но наш обработчик событий не должен быть каждый разrenderПосле этого выполняем привязку и отвязку, что нам и нужноuseEffectсталиcomponentDidMount, его функция возврата становитсяcomponentWillUnmount, здесь нужно использоватьuseEffectВторой параметр функции

Второй параметр useEffect

useEffectВторой параметр , возможны три случая

  1. Ничто не проходит, каждый компонентrenderПозжеuseEffectвызовет, что эквивалентноcomponentDidMountа такжеcomponentDidUpdate
  2. Передача пустого массива [] будет вызываться только один раз, что эквивалентноcomponentDidMountа такжеcomponentWillUnmount
  3. Передавать массив, содержащий переменные, только тогда, когда эти переменные изменяются,useEffectбудет выполнять

См. пример ниже

  function App () {
    const [ count, setCount ] = useState(0)
    const [ width, setWidth ] = useState(document.body.clientWidth)

    const onChange = () => {
      setWidth(document.body.clientWidth)
    }

    useEffect(() => {
      // 相当于 componentDidMount
      console.log('add resize event')
      window.addEventListener('resize', onChange, false)

      return () => {
        // 相当于 componentWillUnmount
        window.removeEventListener('resize', onChange, false)
      }
    }, [])

    useEffect(() => {
      // 相当于 componentDidUpdate
      document.title = count
    })

    useEffect(() => {
      console.log(`count change: count is ${count}`)
    }, [ count ])

    return (
      <div>
        页面名称: { count } 
        页面宽度: { width }
        <button onClick={() => { setCount(count + 1)}}>点我</button>
      </div>
      )
  }

Согласно результатам выполнения приведенного выше примера, первыйuseEffectсередина'add resize event'будет выведен только один раз при первом запуске, независимо от компонентаrender, ни один не будет выводиться, второйuseEffectбудет в каждом компонентеrenderвыполнить после этого,titleменяется при каждом клике, третийuseEffect, пока есть на первом прогоне иcountОн будет выполняться только тогда, когда экран изменится, а экран изменится.renderне влияет на третийuseEffect

useContext

оreactкак использоватьcontext, я не буду здесь вдаваться в подробности, вы можете прочитать то, что я писал ранееРеагировать в контексте использования

contextсерединаProviderа такжеConsumer, который можно использовать как в компонентах класса, так и в компонентах функций,contextTypeМожет использоваться только в компонентах класса, потому что это статическое свойство класса, как его использоватьuseContextХорошо, посмотрите на пример ниже

// 创建一个 context
const Context = createContext(0)

// 组件一, Consumer 写法
class Item1 extends PureComponent {
  render () {
    return (
      <Context.Consumer>
        {
          (count) => (<div>{count}</div>)
        }
      </Context.Consumer>
    )
  }
}
// 组件二, contextType 写法
class Item2 extends PureComponent {
  static contextType = Context
  render () {
    const count = this.context
    return (
      <div>{count}</div>
    )
  }
}
// 组件一, useContext 写法
function Item3 () {
  const count = useContext(Context);
  return (
    <div>{ count }</div>
  )
}

function App () {
  const [ count, setCount ] = useState(0)
  return (
    <div>
      点击次数: { count } 
      <button onClick={() => { setCount(count + 1)}}>点我</button>
      <Context.Provider value={count}>
        <Item1></Item1>
        <Item2></Item2>
        <Item3></Item3>
      </Context.Provider>
    </div>
    )
}

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

написание Преимущества и недостатки
consumer сложная вложенность,ConsumerПервый дочерний узел должен быть функцией, которая незаметно увеличивает нагрузку.
contextType Поддерживает только компоненты класса, не может использоваться в несколькихcontextиспользовать в случае
useContext Нет необходимости в вложенности, большеcontextПросто написать

С вышеупомянутым сравнением, нет причин продолжать использоватьconsumerа такжеcontextType

useMemo

useMemoчто это, сmemoэто имеет значение,memoКонкретный контент можно просмотретьИспользование оптимизации производительности, памятки, PureComponent, shouldComponentUpdate в React,если быть честнымmemoфункциональный компонентPureComponent, используется для оптимизации производительности,useMemoСлишком,useMemoпо моему впечатлению иVueизcomputedВычисляемые свойства похожи тем, что они вычисляют результат на основе значения зависимости.Когда значение зависимости не изменяется, изменение состояния не запускается.useMemoКак его использовать, смотрите в примере ниже

function App () {
  const [ count, setCount ] = useState(0)
  const add = useMemo(() => {
    return count + 1
  }, [count])
  return (
    <div>
      点击次数: { count }
      <br/>
      次数加一: { add }
      <button onClick={() => { setCount(count + 1)}}>点我</button>
    </div>
    )
}

В приведенном выше примереuseMemoТакже поддерживает передачу второго параметра, использование иuseEffectаналогичный

  1. Не передавайте массив, он будет пересчитываться при каждом обновлении
  2. Пустой массив, оценивается только один раз
  3. Зависит от соответствующего значения, при изменении соответствующего значения оно будет пересчитано (можно полагаться на другоеuseMemoвозвращаемое значение)

должны знать о том,useMemoбудет выполняться при рендеринге, а не после рендеринга, что аналогичноuseEffectРазличия есть, так чтоuseMemoНе рекомендуется иметь логику, связанную с побочными эффектами

в то же время,useMemoЕго можно использовать как средство оптимизации производительности, но не воспринимайте его как семантическую гарантию.Reactможет «забыть» некоторые из предыдущихmemoizedзначения и пересчитать их при следующем рендеринге

useCallback

useCallbackчто это, так сказатьuseMemoсинтаксический сахар дляuseCallbackреализовано, можно использоватьuseMemo, В React мы часто сталкиваемся с проблемой оптимизации рендеринга подкомпонентов, подробности можно посмотретьИспользование оптимизации производительности, памятки, PureComponent, shouldComponentUpdate в React, особенно при передаче свойств функции дочерним компонентам, каждый разrenderБудут созданы новые функции, что приведет к ненужному рендерингу подкомпонентов и потере производительности.useCallbackместо использования,useCallbackгарантировано, независимо отrenderСколько раз наши функции являются одной и той же функцией, уменьшая накладные расходы на непрерывное создание, см. следующий пример, как его использовать.

const onClick = `useMemo`(() => {
  return () => {
    console.log('button click')
  }
}, [])

const onClick = useCallback(() => {
 console.log('button click')
}, [])

такой же,useCallbackВторой параметр иuseMemoто же самое, без разницы

useRef

useRefЧто он делает? На самом деле это очень просто. Есть два способа его использования.

  1. Получить экземпляр дочернего компонента (доступны только компоненты класса)
  2. Глобальная переменная в функциональном компоненте, не из-за дублированияrenderПовторное объявление, аналогичное объявлению компонентов классаthis.xxx

Получить экземпляр дочернего компонента

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

// 使用 ref 子组件必须是类组件
class Children extends PureComponent {
  render () {
    const { count } = this.props
    return (
      <div>{ count }</div>
    )
  }
}

function App () {
  const [ count, setCount ] = useState(0)
  const childrenRef = useRef(null)
  // const 
  const onClick = useMemo(() => {
    return () => {
      console.log('button click')
      console.log(childrenRef.current)
      setCount((count) => count + 1)
    }
  }, [])
  return (
    <div>
      点击次数: { count }
      <Children ref={childrenRef}  count={count}></Children>
      <button onClick={onClick}>点我</button>
    </div>
    )
}

useRefПри использовании вы можете передать значение по умолчанию, чтобы указать значение по умолчанию. Когда вам нужно его использовать, посетитеref.currentВы можете получить доступ к экземпляру компонента

свойства компонента класса

В некоторых случаях нам нужно убедиться, что функциональный компонент каждый разrenderПосле этого некоторые переменные не будут объявляться повторно, напримерDomузел, таймерidПодождите, в компоненте класса мы можем полностью сохраниться, добавив в класс кастомное свойство, скажемthis.xxxНо функциональный компонент неthisЕстественно, я не могу использовать этот метод, некоторые друзья говорят, что я могу использоватьuseStateчтобы сохранить значение переменной, ноuseStateвызовет компонентrender, здесь совершенно не нужно, нам просто нужно использоватьuseRefДля достижения см. следующий пример

function App () {
  const [ count, setCount ] = useState(0)
  const timer = useRef(null)
  let timer2 
  
  useEffect(() => {
    let id = setInterval(() => {
      setCount(count => count + 1)
    }, 500)

    timer.current = id
    timer2 = id
    return () => {
      clearInterval(timer.current)
    }
  }, [])

  const onClickRef = useCallback(() => {
    clearInterval(timer.current)
  }, [])

  const onClick = useCallback(() => {
    clearInterval(timer2)
  }, [])

  return (
    <div>
      点击次数: { count }
      <button onClick={onClick}>普通</button>
      <button onClick={onClickRef}>useRef</button>
    </div>
    )
}

Когда мы используем обычную кнопку, чтобы приостановить таймер, обнаруживается, что таймер не может быть очищен, потому чтоAppкомпонент каждый разrender, повторно заявитьtimer2, таймерidво-вторыхrenderпотерян, поэтому таймер не может быть очищен.В этом случае вам нужно использоватьuseRef, чтобы держать таймер для насid, похожий наthis.xxx,ЭтоuseRefдругое использование

useReducer

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

  1. Первый параметр — этоreducer, это функция типа(state, action) => newStateфункция, переходящая в предыдущуюstateи на этот разaction
  2. Второй параметр является начальным.state, который является значением по умолчанию, является более простым методом
  3. Третий параметр — ленивая инициализация, которую можно использовать для вычисленияstateЛогика извлекается вreducerвнешний, который также сбрасывается для будущих парstateизactionоблегчает обработку

См. пример ниже для конкретного использования

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();
  }
}

function App() {
  const [state, dispatch] = useReducer(reducer, {
    count: 0
  });
  return (
    <>
      点击次数: {state.count}
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
  );
}

useImperativeHandle

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

function Kun (props, ref) {
  const kun = useRef()

  const introduce = useCallback (() => {
    console.log('i can sing, jump, rap, play basketball')
  }, [])
  useImperativeHandle(ref, () => ({
    introduce: () => {
      introduce()
    }
  }));

  return (
    <div ref={kun}> { props.count }</div>
  )
}

const KunKun = forwardRef(Kun)

function App () {
  const [ count, setCount ] = useState(0)
  const kunRef = useRef(null)

  const onClick = useCallback (() => {
    setCount(count => count + 1)
    kunRef.current.introduce()
  }, [])
  return (
    <div>
      点击次数: { count }
      <KunKun ref={kunRef}  count={count}></KunKun>
      <button onClick={onClick}>点我</button>
    </div>
    )
}

другие крючки

еще дваhook, говорить не о чем, толку мало, можно глянуть официальную документацию

  1. useLayoutEffect
  2. useDebugValue

пользовательский крючок

Мы обобщили три вопроса ранее,class thisУказывая на проблему, проблема избыточности логики жизненного цикла была решена, и логика трудно повторно использовать.Это не было решено в предыдущем примере.Чтобы решить эту проблему, необходимо настроитьhookрешать

настроитьHook, вы можете извлечь логику компонентов в повторно используемые функции, чтобы решить проблему сложного повторного использования логики

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

function useWidth (defaultWidth) {
  const [width, setWidth] = useState(document.body.clientWidth)

  const onChange = useCallback (() => {
    setWidth(document.body.clientWidth)
  }, [])

  useEffect(() => {
    window.addEventListener('resize', onChange, false)

    return () => {
      window.removeEventListener('resize', onChange, false)
    }
  }, [onChange])

  return width
}

function App () {

  const width = useWidth(document.body.clientWidth)

  return (
    <div> 
      页面宽度: { width }
    </div>
    )
}

Из приведенного выше примера мы можем видеть, что

настроитьhookэто функция, имя которой начинается сuseВ начале внутри функции могут быть вызваны другие функции.hook, почемуuseначинается, потому что, если он не начинается сuseначало,ReactНевозможно определить, содержит ли функция свои внутренниеhookЗвонки,Reactтакже не сможет автоматически проверить вашhookнарушил ли онhookправила, поэтомуuseначало

настроитьhook, это действительно просто, но какую логику нужно извлекать в кастомныеhook, что требует накопленного опыта в работе, чтобы судить, чтобы избежатьhookа такжеhook

Суммировать

в моем обучении и использовании пользовательскихhookВ то время я обнаружил, что причина этого очень проста, многие интерфейсные фреймворки и библиотеки имеют схожие концепции, а дизайн фреймворков и библиотек в конечном итоге одинаков, поэтому, когда мы изучаем новый фреймворк, библиотеку или концепцию, мы должны не Это совершенно новая вещь, и больше должно быть выведено из содержания, которое мы освоили, и по аналогии, чтобы мы могли получить вдвое больший результат с половиной усилий при обучении, и во все более обновляемой области фронтенда, мы можем потратить больше времени, чтобы понять больше основного содержания

Наконец, если эта статья вам чем-то помогла, спасибо за лайк 💗

Ссылаться на

  1. react-1251415695.cos-website.AP-Chengdu.No Money cloud.com/docs/hooks-…
  2. реагировать JS.org/docs/hooks-…

реагировать на другие статьи

  1. Использование границ лени, приостановки и ошибок в React
  2. Использование контекста в React
  3. Использование оптимизации производительности, памятки, PureComponent, shouldComponentUpdate в React