10 минут, чтобы научить вас 8 часто используемым кастомным крючкам вручную

JavaScript React.js
10 минут, чтобы научить вас 8 часто используемым кастомным крючкам вручную

предисловие

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

ты получишь

  • React перехватывает использование основного API и меры предосторожности
  • Реализовать небольшой редукс
  • Реализовать пользовательский useState
  • Реализовать пользовательское использованиеDebounce
  • Реализовать пользовательский useThrottle
  • Реализовать пользовательский useTitle
  • Реализовать пользовательское использованиеUpdate
  • Реализовать пользовательский useScroll
  • Реализовать пользовательское использование мыши
  • Реализовать пользовательскую точку останова createBreakpoint

текст

1. Примечания по использованию базового API хуков реакции

Крючки, обычно используемые автором в проекте, в основном включаютuseState, useEffect, useCallback, useMemo, useRef. конечно нравитсяuseReducer, useContext, createContextЭти хуки также используются в играх H5, потому что нет необходимости поддерживать сложные состояния, поэтому мы можем создать свой собственный небольшой редукс из трех вышеупомянутых API (позже мы расскажем, как реализовать небольшой редукс) для обработки глобального состояния. но для сложных корпоративных проектов нам будет эффективнее использовать редукс и его экологию.

Когда мы используем хуки и функциональные компоненты для написания наших компонентов, первое, что нужно учитывать, это производительность рендеринга.Мы знаем, что если мы используем setState в функциональных компонентах без какой-либо обработки, это вызовет внутреннюю повторную визуализацию компонента.Типичный сценарий :

Когда мы вручную обновляем любое состояние в компоненте-контейнере, каждый подкомпонент внутри контейнера будет повторно визуализирован.Чтобы избежать этой ситуации, мы обычно используем memo для переноса функционального компонента для достижения эффекта pureComponent компонента класса:

import React, { memo, useState, useEffect } from 'react'
const A = (props) => {
  console.log('A1')
  useEffect(() => {
    console.log('A2')
  })
  return <div>A</div>
}

const B = memo((props) => {
  console.log('B1')
  useEffect(() => {
    console.log('B2')
  })
  return <div>B</div>
})

const Home = (props) => {
  const [a, setA] = useState(0)
  useEffect(() => {
    console.log('start')
    setA(1)
  }, [])
  return <div><A n={a} /><B /></div>
}

Когда мы обертываем B мемо, обновление состояния a не приведет к повторному рендерингу компонента B. На самом деле, недостаточно просто оптимизировать это, например, мыДочерний компонент использует переменную или функцию ссылочного типа компонента-контейнера, тогда при обновлении состояния внутри контейнера эти переменные и функции будут переназначены, что вызовет повторный рендеринг, даже если дочерний компонент использует пакет memo, то нам нужно использоватьuseMemoа такжеuseCallback.

useMemoМожет помочь нам кэшировать переменные,useCallbackФункции обратного вызова можно кэшировать, а их второй параметр, как и useEffect, представляет собой массив зависимостей, а необходимость обновления определяется путем настройки массива зависимостей.

import React, { memo, useState, useEffect, useMemo } from 'react'
const Home = (props) => {
  const [a, setA] = useState(0)
  const [b, setB] = useState(0)
  useEffect(() => {
    setA(1)
  }, [])

  const add = useCallback(() => {
    console.log('b', b)
  }, [b])

  const name = useMemo(() => {
    return b + 'xuxi'
  }, [b])
  return <div><A n={a} /><B add={add} name={name} /></div>
}

В этот момент компонент B не будет повторно отображаться после обновления a. Вышеупомянутые шаги оптимизации в основном используются для оптимизации производительности рендеринга компонентов.Обычно мы также включаем приобретение dom компонента и использование внутренних переменных замыкания.В настоящее время мы можем использоватьuseRef.

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

function AutoFocusIpt() {
  const inputEl = useRef(null);
  const useEffect(() => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  }, []);
  return (
    <>
      <input ref={inputEl} type="text" />
    </>
  );
}

В дополнение к приведенным выше сценариям приложений мы также можем использовать его для реализации функции setState компонента класса.Конкретная реализация будет представлена ​​позже.

2. Реализовать небольшой редукс

Для реализации редукса мы будем использовать то, что мы сказали ранее.useReducer, useContext, createContextЧто касается того, как реализовать избыточность для этих трех API, на самом деле существует множество способов их реализации в Интернете.Вот я пишу демонстрацию для вашей справки:

// actionType.js
const actionType = {
  INSREMENT: 'INSREMENT',
  DECREMENT: 'DECREMENT',
  RESET: 'RESET'
}
export default actionType

// actions.js
import actionType from './actionType'
const add = (num) => ({
    type: actionType.INSREMENT,
    payload: num
})

const dec = (num) => ({
    type: actionType.DECREMENT,
    payload: num
})

const getList = (data) => ({
    type: actionType.GETLIST,
    payload: data
})
export {
    add,
    dec,
    getList
}

// reducer.js
function init(initialCount) {
  return {
    count: initialCount,
    total: 10,
    user: {},
    article: []
  }
}

function reducer(state, action) {
  switch (action.type) {
    case actionType.INSREMENT:
      return {count: state.count + action.payload};
    case actionType.DECREMENT:
      return {count: state.count - action.payload};
    case actionType.RESET:
      return init(action.payload);
    default:
      throw new Error();
  }
}

export { init, reducer }

// redux.js
import React, { useReducer, useContext, createContext } from 'react'
import { init, reducer } from './reducer'

const Context = createContext()
const Provider = (props) => {
  const [state, dispatch] = useReducer(reducer, props.initialState || 0, init);
    return (
      <Context.Provider value={{state, dispatch}}>
        { props.children }
      </Context.Provider>
    )
}

export { Context, Provider }

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

3. Внедрение пользовательского useState, поддержка метода setState компонента аналогичного класса.

Друзья, которые знакомы с реакцией, знают, что когда мы используем компонент класса для обновления состояния, setState будет поддерживать два параметра: один — обновленное состояние или обновленное состояние типа обратного вызова, а другой параметр — обновленная функция обратного вызова, как показано ниже. :

this.setState({num: 1}, () => {
    console.log('updated')
})

Однако обратный вызов второго параметра useState функции ловушек поддерживает использование первого параметра setState, аналогичного компоненту класса, и не поддерживает обратный вызов второго параметра, но во многих бизнес-сценариях мы надеемся, что компонент ловушек может поддерживать обновленный обратный вызов. Один из способов, что делать? На самом деле проблема также очень проста. Пока мы очень четко понимаем принцип хуков и API, мы можем добиться этого с помощью пользовательских хуков. Здесь мы используем упомянутые выше useRef и useEffect для взаимодействия с useState для достижения этой функции.

Примечание. Состояние useState реагирующих хуков должно быть размещено на верхнем уровне функционального компонента и не может быть записано в условных операторах, таких как ifelse, чтобы обеспечить согласованность порядка выполнения хуков, потому что нижний уровень useState реализован в структура связанного списка, и есть строгие порядки.

Давайте сначала посмотрим на реализованный код:

import { useEffect, useRef, useState } from 'react'

const useXState = (initState) => {
    const [state, setState] = useState(initState)
    let isUpdate = useRef()
    const setXState = (state, cb) => {
      setState(prev => {
        isUpdate.current = cb
        return typeof state === 'function' ? state(prev) : state
      })
    }
    useEffect(() => {
      if(isUpdate.current) {
        isUpdate.current()
      }
    })
  
    return [state, setXState]
  }

export default useXState

Автор использует характеристики useRef в качестве идентификатора, чтобы различать, монтировать или обновлять.При выполнении setXstate будут переданы те же параметры, что и setState, а обратный вызов будет назначен текущему свойству useRef, чтобы при завершено, мы вручную вызываем текущую, что есть Не очень ли умно реализовать функцию обратного вызова после обновления?

4. Реализуйте пользовательский useDebounce

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

// 节流
function throttle(func, ms) {
    let previous = 0;
    return function() {
        let now = Date.now();
        let context = this;
        let args = arguments;
        if (now - previous > ms) {
            func.apply(context, args);
            previous = now;
        }
    }
}

// 防抖
function debounce(func, ms) {
    let timeout;
    return function () {
        let context = this;
        let args = arguments;

        if (timeout) clearTimeout(timeout);
        
        timeout = setTimeout(() => {
            func.apply(context, args)
        }, ms);
    }
}

Итак, давайте сначала реализуем хуки анти-шейка, код выглядит следующим образом:

import { useEffect, useRef } from 'react'

const useDebounce = (fn, ms = 30, deps = []) => {
    let timeout = useRef()
    useEffect(() => {
        if (timeout.current) clearTimeout(timeout.current)
        timeout.current = setTimeout(() => {
            fn()
        }, ms)
    }, deps)

    const cancel = () => {
        clearTimeout(timeout.current)
        timeout = null
    }
  
    return [cancel]
  }

export default useDebounce

Из кода видно, что useDebounce принимает три параметра, а именно функцию обратного вызова, временной интервал и массив зависимостей.Он предоставляет API отмены, который в основном используется для управления тем, когда останавливать функцию устранения дребезга. Конкретное использование заключается в следующем:

// ...
import { useDebounce } from 'hooks'
const Home = (props) => {
  const [a, setA] = useState(0)
  const [b, setB] = useState(0)
  const [cancel] = useDebounce(() => {
    setB(a)
  }, 2000, [a])

  const changeIpt = (e) => {
    setA(e.target.value)
  }
  return <div>
    <input type="text" onChange={changeIpt} />
    { b } { a }
  </div>
}

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

5. Реализуйте пользовательский useThrottle

Таким же образом продолжаем реализовывать функцию throttling hooks. Перейдите непосредственно к коду:

import { useEffect, useRef, useState } from 'react'

const useThrottle = (fn, ms = 30, deps = []) => {
    let previous = useRef(0)
    let [time, setTime] = useState(ms)
    useEffect(() => {
        let now = Date.now();
        if (now - previous.current > time) {
            fn();
            previous.current = now;
        }
    }, deps)

    const cancel = () => {
        setTime(0)
    }
  
    return [cancel]
  }

export default useThrottle

Код аналогичен пользовательскому useDebounce, но следует отметить, что для реализации функции отмены мы используем внутреннее состояние для обработки и отменяем эффект дросселирования, контролируя временной интервал.Конечно, есть много других способов. для реализации этого API хуков. Конкретные эффекты следующие:

6. Реализуйте пользовательский useTitle

На самом деле существует много сценариев использования пользовательских хуков useTitle, потому что большинство наших текущих проектов разрабатываются по пути SPA или гибридного SPA, Для разных маршрутов мы также надеемся переключиться на соответствующий заголовок, например, многостраничное приложение, которое может сделать так, чтобы пользователи лучше знали тему и содержание страницы. Реализация этого хука тоже очень проста, переходим непосредственно к коду:

import { useEffect } from 'react'

const useTitle = (title) => {
    useEffect(() => {
      document.title = title
    }, [])
  
    return
  }

export default useTitle

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

const Home = () => {
    // ...
    useTitle('趣谈前端')
    
    return <div>home</div>
}

7. Реализовать настраиваемое useUpdate

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

import { useState } from 'react'

const useUpdate = () => {
    const [, setFlag] = useState()
    const update = () => {
        setFlag(Date.now())
    }
  
    return update
  }

export default useUpdate

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

const Home = (props) => {
  // ...
  const update = useUpdate()
  return <div>
    {Date.now()}
    <div><button onClick={update}>update</button></div>
  </div>
}

Эффект следующий:

8. Реализуйте пользовательский useScroll

Пользовательский useScroll также является одной из часто встречающихся проблем. Мы часто отслеживаем изменение положения прокрутки элемента, чтобы решить, какой контент отображать. Этот сценарий приложения широко используется в разработке игр H5. Теперь давайте посмотрим на реализацию код:

import { useState, useEffect } from 'react'

const useScroll = (scrollRef) => {
  const [pos, setPos] = useState([0,0])

  useEffect(() => {
    function handleScroll(e){
      setPos([scrollRef.current.scrollLeft, scrollRef.current.scrollTop])
    }
    scrollRef.current.addEventListener('scroll', handleScroll, false)
    return () => {
      scrollRef.current.removeEventListener('scroll', handleScroll, false)
    }
  }, [])
  
  return pos
}

export default useScroll

Как видно из приведенного выше кода, нам нужно передать ссылку на элемент в функции хука, которую мы можем получить, используя ref и useRef в функциональном компоненте, хук возвращает значения x и y элемента прокрутка, то есть левое смещение и верхняя часть прокрутки.Смещение, конкретное использование заключается в следующем:

import React, { useRef } from 'react'

 import { useScroll } from 'hooks'
const Home = (props) => {
  const scrollRef = useRef(null)
  const [x, y] = useScroll(scrollRef)

  return <div>
      <div ref={scrollRef}>
        <div className="innerBox"></div>
      </div>
      <div>{ x }, { y }</div>
    </div>
}

Используя useScroll, хук поможет нам автоматически отслеживать изменения полосы прокрутки контейнера, чтобы получать положение прокрутки в режиме реального времени. Конкретные эффекты следующие:

9. Реализовать пользовательское useMouse и реализовать пользовательскую точку останова createBreakpoint.

Методы реализации кастомных useMouse и createBreakpoint аналогичны useScroll.Они оба прослушивают события окна или dom для автоматического обновления нужных нам значений.Я не буду реализовывать их здесь по одному.Если вы не поняли , вы можете связаться со мной. С помощью этих настраиваемых хуков можно значительно повысить эффективность разработки нашего кода, а повторяющийся код можно эффективно использовать повторно, чтобы каждый мог попробовать больше на работе.

Когда мы пишем много пользовательских хуков, хорошим опытом разработки является управление и распространение этих хуков унифицированным образом.Автор предлагает создать в проекте отдельный каталог хуков для хранения этих повторно используемых хуков для удобства управления и обслуживания. следующим образом:

наконец

Если вы хотите получитьПолный исходный код этого проектаили хотите узнать большеигра Н5, webpack,node,gulp,css3,javascript,nodeJS,визуализация данных холстаДругие передовые знания и практические, добро пожаловать, чтобы присоединиться к нашей группе технологий в общественном № "Кое-что о переднем конце" учиться вместе, чтобы обсудить, изучить границы переднего конца.

Больше рекомендаций