Мы представили в первую
Mixin HOC Render Props
, а затем ввести еще один очень важный状态逻辑复用模式
,Этоreact-hooks
Серия React — Mixin, HOC, Render Props (Часть 1)
Серия React — легко выучить хуки (средний уровень)
Серия React — Пользовательские хуки — это просто (часть 2)
HOC, Render Props, комбинация компонентов, перенос ссылки... Почему повторное использование кода так сложно? ,
根本原因在于细粒度代码复用不应该与组件复用捆绑在一起
Другими словами, режимы, о которых мы упоминали ранее, являются режимами верхнего уровня, исследуемыми в соответствии с существующими правилами игры (механизм компонентов).
❗️❗️HOC、Render Props 等基于组件组合的方案
,相当于先把要复用的逻辑包装成组件
,再利用组件复用机制实现逻辑复用。
Естественно, это ограничено повторным использованием компонентов, поэтому возникают такие проблемы, как ограниченная масштабируемость, раздел Ref, Wrapper Hell и т. д.
🤔Прямое повторное использование кода
Подумайте, нужно ли нам переиспользовать кусок логики в нашей обычной разработке, или не извлекать функцию, например ту, что используется防抖函数、获取token函数
Но для реакции复用逻辑不同
, Пока нет хуков, функция не может поддерживать состояние внутри, поэтому режим извлечения его в функцию кажется невозможным, но на самом деле это можно сделать.
// 设置提示语tip
export const setTip = function (context: any) {
// vote-tip提示
const tipVisible = JSON.parse(localStorage.getItem('tipVisible') as string)
if (Object.is(tipVisible, null)) {
localStorage.setItem('tipVisible', 'true')
} else if (Object.is(tipVisible, false)) {
context.setState({
tipVisible: false
})
}
}
Например, в развитии бизнеса я стараюсь关联到state复用逻辑
Разделены, как и базовые служебные функции, здесьcontext
Собственно это текущий компонент, то есть я передаюthis
чтобы функция поддерживалаstate
, но такой код трудно поддерживать, потому что你可能找不到它们的关联性
Крючки появились
Решил проблему повторного использования логики состояния из режима Mixin, HOC, Render Props, но не решил проблему повторного использования принципиально,函数应是代码复用的基本单位,而不是组件
, так почемуhook是颠覆性的
, потому что это по существу решает проблему повторного использования логики состояния, с函数作为最小的复用单位
, а не компонент
Что такое крючки?
Официальное введение: Hook — это новая функция React 16.8. Он позволяет вам использовать состояние и другие функции React без написания классов.
Что такое функциональный компонент
Функциональный компонент — это просто процесс выполнения функции для получения возвращаемого значения, что легко понять:
state变化,函数组件执行,触发render更新视图
, по-прежнему отличается от компонента класса, компонент классаstate变化,触发render方法更新而不是
, что это значит❓, свойства, представляющие компоненты класса, не будут повторно объявляться, а компоненты функций будут перевыполняться каждый раз при изменении состояния, и объявление будет повторяться, вот почему это необходимоuseMemo
иuseCallBack
Эти два крючка, мы поговорим о следующем
const Counter=()=>{
const [
number,
setNumber
] = useState(0)
console.log("我触发执行了")
return (
<>
<p>{number}</p>
<button
onClick={
() => setNumber(number + 1)
}
>
改数字
</button>
</>
)
}
Еще один интересный момент: если мы используем компоненты класса в разработке, нам нужно следоватьthis
Работа с, но использование Крюка помогло нам избавиться отthis场景问题
, но это создает другую проблему, вы используете функцию, тогда она, естественно, будет следовать闭包
Имея дело с чем-то, что вы неосознанно陷入闭包陷阱
(я расскажу об этом позже), это потрясающе羁绊
,но闭包
Есть так много преимуществ
Функция памяти или функция кэширования❓
Реализация react-hook неотделима от
记忆函数(也称做缓存函数)
или следует сказать, чтобы извлечь выгоду из闭包
, давайте реализуем记忆函数
Бар
const memorize = function(fn) {
const cache = {} // 存储缓存数据的对象
return function(...args) { // 这里用到数组的扩展运算符
const _args = JSON.stringify(args) // 将参数作为cache的key
return cache[_args] || (cache[_args] = fn.apply(fn, args)) // 如果已经缓存过,直接取值。否则重新计算并且缓存
}
}
есть тест:
const add = function(a) {
return a + 1
}
const adder = memorize(add)
adder(1) // 2 cache: { '[1]': 2 }
adder(1) // 2 cache: { '[1]': 2 }
adder(2) // 3 cache: { '[1]': 2, '[2]': 3 }
useState
зачем использовать
В процессе разработки мы часто сталкиваемся с тем, что когда функциональный компонент хочет иметь собственное поддерживаемое состояние, его необходимо преобразовать в
class
Появление useState: useState — это хук, который позволяет вам добавлять состояние к компонентам функции React.在函数组件里面使用 class的setState
как пользоваться
useState принимает один параметр и возвращает массив
// 使用es6解构赋值,useState(0)的意思是给count赋予初始值0
// count是一个状态值,setCount是给这个状态值进行更新的函数
const [count, setCount] = useState(0);
Например 🌰:
import React, { useState } from 'react'
function Example() {
// 声明一个叫 "count" 的 state 变量
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Сбор очков знаний
значение инициализации
useState的初始值,只在第一次有效
Сценарий; нажмите кнопку, чтобы обновить количество дочерних компонентов
const Child = ({data}) =>{
const [count, setCount] = useState(data)
return (
<div>
<div>child</div>
<div>count:{count} --- data:{data}</div>
</div>
);
}
const Parent =()=>{
const [data, setData] = useState(0)
return(
<div>
<div>
{data}
</div>
<button onClick={()=>setData(data+1)}>更新data</button>
<Child data={data}/>
</div>
)
}
есть тест:
// 点击按钮
<div>count:0 --- data:1</div>
Обновление является прямой заменой
useState返回更新state的函数
В отличие от this.setState компонентов класса, он не объединяет новое состояние со старым состоянием, а直接替换
,相当于直接返回一个新的对象
, так это тоже闭包陷阱产生的原因之一
let testUser={name:"vnues"} // 定义全局变量
const Parent =()=>{
const [user, setUser] = useState(testUser)
if(user!==testUser){
testUser=user
console.log(testUser)
}
return(
<div>
<button onClick={()=>setCount({age:18})}>更新data</button>
<Child data={data}/>
</div>
)
}
есть тест:
// 点击按钮
testUser:{age:18}
Как видите, функция упирается в условие if, что это значит,说明user和testUser的指向不同了
, доказательство直接替换
В общем, повторное выполнение функции означает повторную инициализацию состояния и объявления, поэтому мне любопытно,
hook
Как сохранить последнее состояние, посмотрим, как это работает
let memoizedStates = [] // 存储state
let index = 0
function useState (initialState) {
// 判断memoizedStates有没有缓存值,没有则还是个初始化的useState
memoizedStates[index] = memoizedStates[index] || initialState
let currentIndex = index
function setState (newState) {
memoizedStates[currentIndex] = newState // 直接替换
render() // 进行视图更新
}
return [memoizedStates[index++], setState]
}
function render() {
index = 0 // 重新执行函数组件,重新清零
ReactDOM.render(<Counter />, document.getElementById('root'));
}
❗️ Обратите внимание на код выше,有个 index=0 的操作,因为添加的时候按照顺序添加的,渲染的时候也要按照顺序渲染的。
, поэтому мы не можем писать хуки в циклах или суждениях
Например 🌰
const Test=()=>{
const [count, setCount] = useState(0)
// 只执行一次
useEffect(()=>{
setCount(100)
},[])
return (<div>{count}</div>)
}
函数组件Test
Запустите следующим образом:
так поймиuseState原理有助于我们日常开发中解决bug
useEffect
Эффект-хук позволяет выполнять побочные эффекты в функциональных компонентах,
Что такое побочный эффект
Побочные эффекты — это концепция функционального программирования, вучебник по функциональному программированию, следующее поведение называется побочным эффектом
- изменить переменную
- Изменить значение поля объекта
- Выбросить исключение
- Отображение информации на консоли, получение ввода с консоли
- Отображение на экране (GUI)
- Чтение и запись файлов, сетей, баз данных.
зачем использовать
Появление Effect Hook: Дело в том, что вы можете использовать функцию жизненного цикла класса в функциональном компоненте, вы можете думать об этом какcomponentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合
(Официальное продолжение будет реализовывать другие функции жизненного цикла, так что следите за обновлениями), еще один момент заключается в том, что вы можете сосредоточиться на обработке副作用操作(比如网络请求,DOM操作等)
как пользоваться
useEffect(fn, []) // 接收两个参数 一个是回调函数 另外一个是数组类型的参数(表示依赖)
❗️❗️Примечание. Время выполнения useEffect: React гарантирует, что при каждом запуске эффекта DOM обновляется,默认情况下,useEffect 会在每次渲染后都执行
, , который выполняется после первого рендера и после каждого обновления,我们可以根据依赖项进行控制
Сбор очков знаний
useEffect принимает только функции
// ❌因为async返回的是个promise对象
useEffect(async() => {
const data = await getAjax()
})
// 建议😄
useEffect(() => {
// 利用匿名函数
(async()=>{
const data = await getAjax()
})()
})
Жизненный цикл макета React
constructor
: Компоненты функций не нуждаются в конструкторе. Вы можете инициализировать состояние, вызвав useState.componentDidMount
: реализован второй параметр, переданный через useEffect.componentDidUpdate
: передать массив, второй параметр которого пуст или значение которого изменяется с помощью useEffect.componentWillUnmount
: В основном используется для устранения побочных эффектов. Смоделируйте его, вернув функцию из функции useEffect.shouldComponentUpdate
: вы можете обернуть компонент с помощью React.memo, чтобы сделать поверхностное сравнение его свойств. для имитации необходимости обновления компонента.componentDidCatch and getDerivedStateFromError
: Эквиваленты хуков для этих методов в настоящее время недоступны, но скоро будут добавлены.
// 模拟 componentDidMount
useEffect(()=>{
// 逻辑
},[])
// 模拟componentDidUpdate
useEffect(fn)
Метод $watch Mock Vue
useEffect(fn,[user]) // 对user做监控
Разделение проблем с использованием нескольких эффектов
Точно так же, как вы можете использовать хуки с несколькими состояниями, вы также можете использовать несколько эффектов. Это разделяет несвязанную логику на разные эффекты
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
}
Как убрать побочные эффекты
В компонентах React есть два распространенных побочных эффекта:
需要清除的和不需要清除的
Эффекты, которые не нужно очищать
Иногда мы просто хотим запустить дополнительный код после того, как React обновит DOM.
比如发送网络请求,手动变更 DOM,记录日志,这些都是常见的无需清除的操作。
Потому что после того, как мы выполнили эти операции,可以忽略他们了
.
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Эффект, который нужно убрать
Ранее мы рассмотрели, как использовать побочные эффекты, которые не нужно очищать, и некоторые, которые требуют очистки.例如订阅外部数据源。
В этом случае работа по очистке очень важна для предотвращения утечек памяти!
Как я могу очистить: в случае использования
回调函数return一个取消订阅的函数
useEffect(() => {
// 订阅
ChatAPI.subscribeToFriendStatus(props.id, handleStatusChange);
return () => {
// 取消订阅
ChatAPI.unsubscribeFromFriendStatus(props.id, handleStatusChange);
};
});
useEffect и ловушка закрытия
Ловушка закрытия: Когда мы разрабатываем React Hooks, значения, определенные useState, — не последнее явление.
Давайте рассмотрим сценарий: мы хотим, чтобы счетчик продолжал увеличиваться на 1.
const App = () => {
const [count, setCount] = useState(0)
useEffect(() => {
const timeId = setInterval(() => {
console.log(count)
setCount(count + 1)
}, 1000)
return () => { clearInterval(timeId) }
}, [])
return (
<span style={{ fontSize: 30, color: "red" }}>{count}</span>
)
}
Как показано выше,useEffect
Доступ к функции обратного вызоваApp函数
Переменныеcount
,形成了闭包Closure(App)
Посмотрим на результат:
Счетчик не увеличивается сам на +1 и обновляет дом каждую секунду, как ожидалось, а после меняется с 0 на 1. счет всегда1
,并不会一直加下去
, что является общим闭包陷阱
Причина в том, чтоuseEffect(fn,[])
Выполняется только один раз (больше не выполняется позже),setInterval里的回调函数与APP函数组件形成了闭包,count为0
, затем выполнитеsetCount
Операция, изменение состояния, функциональный компонент Повторное выполнение приложения, выполнениеconst [count, setCount] = useState(0)
, вы можете понимать это как повторное объявлениеcount变量
,也就是说setInterval里访问的count变量跟这次重新声明的count变量无关(❗️理解这句话很重要)
, мы можем немного изменить его,useEffect(fn,[])
Он выполняется только один раз, то есть переменная count больше не берется при первом получении, мы удаляем зависимость и снова получаем count после его обновления.
const App = () => {
const [count, setCount] = useState(0)
useEffect(() => {
const timeId = setInterval(() => {
console.log(count)
setCount(count + 1)
}, 1000)
return () => { clearInterval(timeId) }
})
return (
<span style={{ fontSize: 30, color: "red" }}>{count}</span>
)
}
👆 Вы видите, что эффект достигнут
Если вы не понимаете вышесказанное, давайте посмотрим на другой пример, чтобы было понятно:
const App = () => {
const [user, setUser] = useState({ name: "vnues", age: 18 })
useEffect(() => {
const timeId = setInterval(() => {
console.log(user)
setUser({ name: "落落落洛克", age: user.age + 1 })
}, 1000)
return () => { clearInterval(timeId) }
}, [])
return (
<span style={{ fontSize: 30, color: "red" }}>{user.name}{user.age}</span>
)
}
⚠️Сказанное выше указывает на то, что операция setUser является прямой заменой. Кроме того, ниже мы представим несколько способов решения ловушки замыкания.
useRef
useRef возвращает изменяемый объект ref, чей
.current
Свойство инициализируется входящим параметром (initialValue),另外ref对象的引用在整个生命周期保持不变
зачем использовать
useRef можно использовать для
访问DOM节点或React组件实例和作为容器保存可变变量
.
как пользоваться
const refContainer = useRef(initialValue)
Сбор очков знаний
Получить узел элемента DOM
useRef возвращает изменяемый объект ref, свойство .current которого равно
初始化为传入的参数(initialValue)
,пройти черезcurrent
свойства могут быть доступныDOM节点
const App=()=>{
const inputRef = useRef(null);
console.log(inputRef) // 没有访问到 此时dom还未挂载
useEffect(() => {
console.log(inputRef) // dom挂载完毕
}, [])
return <div>
<input type="text" ref={inputRef} />
</div>
}
Результат выглядит следующим образом:
Примечание: createRef возвращает новую ссылку каждый раз при рендеринге, а useRef каждый раз возвращает одну и ту же ссылку. Конкретно о(ref React.createRef useRef、React.forwardRef这些形式我会单独抽一个章节来讲到)
Получить экземпляр дочернего компонента
// 实际就是利用了一个回调
const Child2 = React.forwardRef((props, ref) => {
return <button ref={ref}>Child2</button>
})
const App = () => {
const childRef = useRef();
const child2Ref = useRef()
useEffect(() => {
console.log('useRef')
console.log(childRef.current)
childRef.current.handleLog();
console.log("child2Ref", child2Ref)
}, [])
return (
<div>
<h1>Hello World!</h1>
<Child ref={childRef} count="1" />
<Child2 ref={child2Ref} />
</div>
)
}
// 因为函数组件没有实例,如果想用ref获取子组件的实例,子组件组要写成类组件
class Child extends Component {
handleLog = () => {
console.log('Child Component');
}
render() {
const { count } = this.props;
return <h2>count: { count }</h2>
}
}
результат:
Обратите внимание на один момент: экземпляр компонента не является примером функциональных компонентов для компонентов класса.使用
React.forwardRefAPI是转发
ref拿到子组件的DOM中想要获取的节点,并不是获取实例
,因为函数组件没有实例这一概念
,
Контейнер для хранения изменяемых переменных
Помните
useRef
не только для полученияDOM节点和组件实例
, и еще одно умное использование作为容器保留可变变量
, можно сказать так:无法自如地使用useRef会让你失去hook将近一半的能力
официальное заявление:useRef работает не только со ссылками DOM. Объект "ref" является общим контейнером,其 current 属性是可变的,可以保存任何值(可以是元素、对象、基本类型、甚至函数)
Давайте посмотрим на анализ 👇:
И в компоненте класса, и в компоненте функции у нас есть два метода в
re-render
Хранить данные между (повторно отображать):
в компоненте класса
В состоянии компонента: при каждом изменении состояния компонент перерисовывается.
В переменной экземпляра:
变量的引用
Остается неизменным в течение всего срока службы компонента.
Изменения переменных экземпляра не приводят к повторному рендерингу.
в функциональном компоненте
Использование хуков в компонентах-функциях может достичь эффекта, эквивалентного компонентам-классам:
В состоянии:
使用useState或useReducer
. Обновления состояния вызовут повторную визуализацию компонента.В ref (ref возвращается с помощью useRef): Эквивалентно
类组件中的实例变量
, изменение имущества. Союза не приведет к восстановлению. (эквивалентно ❗️❗️ref对象充当this,其current属性充当实例变量
)
const App = () => {
const counter = useRef(0);
const handleBtnClick = () => {
counter.current = counter.current + 1;
console.log(counter)
}
console.log("我更新啦")
return (
<>
<h1>{`The component has been re-rendered ${counter.current} times`}</h1>
<button onClick={handleBtnClick}>点击</button>
</>
);
}
Замените локальные переменные функциональными компонентами
// const name="vnues"
const App = () => {
const [count, setCount] = useState(0)
const name="vnues" // 声明局部变量
const handleBtnClick = () => {
setCount(count+1)
}
return (
<>
<span>{count}</span>
<button onClick={handleBtnClick}>点击</button>
</>
)
}
Иногда у нас возникает такая ситуация, и нам нужно объявить переменную для сохранения значения,但是如果函数组件state变化,函数重新执行
, приведет к повторному объявлениюname
, очевидно, не нужно, некоторые студенты сказали, что его можно разместить под общей ситуацией, чтобы избежать ненужных повторных деклараций, что на самом деле является решением, но если оно не будет переработано вовремя,容易造成内存泄漏
, мы можем воспользоваться характеристиками контейнера Ref и использовать текущий для сохранения可变的变量
const App = () => {
const [count, setCount] = useState(0)
const ref=useRef("vnues") // 利用容器的特点
const handleBtnClick = () => {
setCount(count+1)
}
return (
<span>{count}</span>
<button onClick={handleBtnClick}>点击</button>
)
}
реферальные ссылки остаются без изменений
Поскольку useRef возвращает ссылку на объект ref в
FunctionComponent 生命周期
Он остается неизменным и сам используется как контейнер для хранения переменных переменных, поэтому мы можем использовать эти функции для выполнения множества операций, что похоже наuseState
разные
Решение ловушки замыкания
Вы можете понять это так: countRef здесь эквивалентен глобальной переменной, одна модифицируется, а другая обновляется...
const App = () => {
const [count, setCount] = useState(0)
const countRef = useRef(count)
useEffect(() => {
const timeId = setInterval(() => {
countRef.current = countRef.current + 1
setCount(countRef.current)
}, 1000)
return () => clearInterval(timeId)
}, [])
return (
<span>{countRef.current}</span>
)
}
результат:
Получить реквизит или состояние предыдущего раунда
Ref может не только получить ссылку на компонент, создать объект побочного эффекта Mutable, но и сохранить более старое значение с помощью useEffect, которое чаще всего используется для получения previousProps.React официально использует Ref для инкапсуляции простых хуков для получения последнего значения:
const usePrevious=(value)=>{
const ref = useRef()
useEffect(() => {
ref.current = value
});
return ref.current
}
Поскольку useEffect выполняется после завершения рендеринга, значение ref всегда является последним рендером в текущем рендере, и мы можем использовать его для получения последних реквизитов или состояния.
Обновление Ref является побочным эффектом
В официальной документации говорится: "Если вы не выполняете ленивую инициализацию, избегайте установки ссылок во время рендеринга - это может привести к неожиданному поведению. Вместо этого обычно вы хотите изменить ссылки в обработчиках событий и эффектах".
Проще говоря, обновление Ref — это побочный эффект, и мы не должныrender-parse(渲染阶段)
обновление, но вuseEffect或者useLayoutEffect
выполнять побочные эффекты
Давайте сначала посмотрим на диаграмму жизненного цикла FunctionComponent:
Из рисунка видно, что вRender phase 阶段
Не разрешается делать «побочные эффекты», то есть писать код побочных эффектов, потому что этот этап может быть отменен или переделан движком React в любое время.
Модификация Ref является побочной операцией, поэтому она не подходит для данного этапа. Мы можем видеть это вCommit phase 阶段
могу сделать это
// ❌的写法
const RenderCounter = () => {
const counter = useRef(0);
// Since the ref value is updated in the render phase,
// the value can be incremented more than once
counter.current = counter.current + 1;
return (
<h1>{`The component has been re-rendered ${counter} times`}</h1>
);
};
// 正确✅的写法
const RenderCounter = () => {
const counter = useRef(0);
useEffect(() => {
// 每次组件重新渲染,
// counter就加1
counter.current = counter.current + 1;
});
return (
<h1>{`The component has been re-rendered ${counter} times`}</h1>
);
};
useCallback
Хуки возвращают запомненную функцию обратного вызова,❗️
根据依赖项来决定是否更新函数
зачем использовать
react中Class的性能优化
. До рождения хуков, если компонент содержал внутреннее состояние, мы создавали компонент на основе класса. В реакции точка оптимизации производительности:
- Вызов setState вызовет повторную визуализацию компонента, независимо от того, отличается ли состояние до и после.
- При обновлении родительского компонента дочерний компонент также будет автоматически обновлен.
Основываясь на двух вышеуказанных пунктах, наше обычное решение:
использовать
immutable
Сравните и вызовите setState, когда они не равнысуществует
shouldComponentUpdate
среднее суждение前后的props和state
, если изменений нет, вернуть false, чтобы предотвратить обновление
После выхода крючков我们能够使用function的形式来创建包含内部state的组件
. Однако, используя форму функции,失去了上面的shouldComponentUpdate
, мы не можем решить, нужно ли обновлять, оценивая состояние до и после. И, в функциональных компонентах, реагировать不再区分mount和update两个状态
, что означает, что компонент функции每一次调用都会执行其内部的所有逻辑
, это приведет к большой потере производительности. следовательноuseMemo 和useCallback
используется для оптимизации проблем с производительностью
Например 🌰:
const set = new Set() // 借助ES6新增的数据结构Set来判断
export default function Callback() {
const [count, setCount] = useState(1);
const [val, setVal] = useState('');
const callback =() => {
console.log(count);
}
set.add(callback)
return <div>
<h4>{count}</h4>
<h4>{set.size}</h4>
<button onClick={() => setCount(count + 1)}>+</button>
</div>;
}
как пользоваться
Пучок
内联回调函数及依赖项数组作为参数传入
useCallback, который вернет запомненную версию функции обратного вызова, которая仅在某个依赖项改变时才会更新
. Когда вы передаете функцию обратного вызова в оптимизированный и используете ссылочное равенство для避免非必要渲染
(например, shouldComponentUpdate), это будет очень полезно.
⚠️ Он основан не на функции обратного вызова fn, переданной до и после, чтобы сравнить, равны ли они, а на том, чтобы обновить функцию обратного вызова fn в соответствии с зависимостями, автор сначала подумал неправильно
const memoizedCallback = useCallback(fn, deps)
Сбор очков знаний
Параметры зависимости useCallback
Функция обратного вызова fn только某个依赖项改变时才会更新
,如果没有任何依赖项,则deps为空
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
)
useCallback отлично работает с React.memo
Сценарий: есть родительский компонент, который содержит дочерние компоненты, и дочерний компонент получает функцию в качестве реквизита; обычно, если родительский компонент обновляется, дочерний компонент также выполняет обновление; но в большинстве сценариев обновление не требуется , вы можете использовать useCallback для возврата функции, а затем передать эту функцию дочернему компоненту в качестве реквизита; таким образом, дочерний компонент может избежать ненужных обновлений.
React.memo 为高阶组件
. это сReact.PureComponent 非常相似
,但只适用于函数组件,而不适用 class 组件
,能对props做浅比较,防止组件无效的重复渲染
// 父组件
const Parent=()=>{
const [count, setCount] = useState(1);
const [val, setVal] = useState('');
const callback = useCallback(() => {
return count;
}, [count]);
return <div>
<h4>{count}</h4>
<Child callback={callback}/>
<div>
<button onClick={() => setCount(count + 1)}>+</button>
<input value={val} onChange={event => setVal(event.target.value)}/>
</div>
</div>;
}
// 子组件
const Child=({ callback })=>{
const [count, setCount] = useState(() => callback());
useEffect(() => {
setCount(callback());
}, [callback]);
return <div>
{count}
</div>
}
export default React.memo(Child) // 用React.memo包裹
если你的函数组件在给定相同 props 的情况下渲染相同的结果
,Так你可以通过将其包装在 React.memo 中调用
, так что通过记忆组件渲染结果的方式来提高组件的性能表现
. Это означает, что в данном случаеReact 将跳过渲染组件的操作并直接复用最近一次渲染的结果。
useMemo
и
useCallback
похоже, разница естьuseCallback
вернутьmemoized函数
,иuseMemo
вернутьmemoized 值
❗️Вы можете думать так:
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。
зачем использовать
и зачем использовать
useCallback
Аналогично, еще один момент - кеш昂贵的计算
(Избегайте дорогостоящих вычислений при каждом рендеринге)
export default function WithMemo() {
const [count, setCount] = useState(1);
const [val, setValue] = useState('');
// 缓存了昂贵的计算
const expensive = useMemo(() => {
console.log('compute');
let sum = 0;
for (let i = 0; i < count * 100; i++) {
sum += i;
}
return sum;
}, [count]);
return <div>
<h4>{count}-{expensive}</h4>
{val}
<div>
<button onClick={() => setCount(count + 1)}>+c1</button>
<input value={val} onChange={event => setValue(event.target.value)}/>
</div>
</div>;
Выше мы видим, что useMemo используется для выполнения дорогостоящих вычислений, затем возвращает значение вычисления и передает счетчик в качестве зависимого значения. Таким образом, дорогостоящее выполнение будет запускаться только при изменении счетчика, а последнее кэшированное значение будет возвращено при изменении val.
как пользоваться
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
// computeExpensiveValue得有返回值
Сбор очков знаний
useMemo отлично работает с React.memo
const Couter = React.memo(function Couter(props) {
console.log('Couter render')
return (
<>
<span>{count}</span>
<div onClick={props.clickHandle}>{props.count}</div>
</>
)
})
const App=()=> {
const [count, setCount] = useState(0);
const db = useMemo(() => {
return count * 2;
}, [count])
return (
<div>
<Couter count={count}></Couter>
<div>{db}</div>
<button onClick={() => { setCount(count + 1) }}>点击</button>
</div>
)
}
оптимизирован для производительности
Некоторые студенты будут думать, чтоuseCallback
иuseMemo
Это так просто в использовании, могу ли я оптимизировать свои будущие проекты, как написано в следующем примере🌰
const App = () => {
const [count, setCount] = useState(0);
/**
* 对所有的方法我都采用useCallback做缓存优化
* const handleBtn=()=>{
* setCount(count + 1)
* }
*/
const handleBtn = useCallback(() => {
setCount(count + 1)
}, [count])
/**
* 对所有的局部变量我都采用useMemo做缓存优化
* const db=count*2
*/
const db = useMemo(() => {
return count * 2;
}, [count])
return (
<div>
<Couter count={count}></Couter>
<div>{db}</div>
<button onClick={() => { handleBtn }}>点击</button>
</div>
)
}
❗️Автор тоже так думал в начале, если вы действительно будете развивать наш проект таким образом, то поздравляю, вы умрёте с треском
Почему useCallback и useMemo хуже
Оптимизация производительности не бесплатна, она всегда сопряжена с затратами, но не всегда дает преимущества, чтобы компенсировать затраты, поэтому мы принимаем
useCallback和useMemo
Чтобы сделать оптимизацию производительности, это должно быть сделано花费的成本大于收入的成本
Во-первых, нам нужно знатьuseCallback,useMemo
Также есть накладные расходы.useCallback,useMemo
Будет «запоминать» некоторые значения, и при последующих рендерах将依赖数组中的值取出来和上一次记录的值进行比较
, если они не равны, функция обратного вызова будет выполнена повторно, в противном случае будет возвращено «запомненное» значение напрямую.这个过程本身就会消耗一定的内存和计算资源
. Поэтому чрезмерное использованиеuseCallback,useMemo
возможный会影响程序的性能
,并且也加大了维护成本,毕竟代码更加复杂化了
Когда использовать useMemo и useCallback?
Используйте useMemo и useCallback для обеих целей.
сохранить ссылки равными
Для объектов, массивов, функций и т. д., используемых внутри компонентов, если они используются в массивах зависимостей других хуков или передаются нижестоящим компонентам в качестве реквизита, вы должны использовать
useMemo 和 useCallback
自定义 Hook 中暴露出来的 object、array、函数等
, оба должны использоватьuseMemo 和 useCallback
, чтобы гарантировать, что при том же значении ссылка не изменится (вы можете понимать это как вывод первого утверждения,即自定义hooks比作组件
, потому что функциональный компонент повторно выполнит функцию, как только состояние изменится)дорогой расчет
Такие как 👆Пример🌰
expensive函数
Сценарии без useMemo и useCallback
Если возвращаемое значение является исходным значением:
string, boolean, null, undefined, number, symbol(不包括动态声明的 Symbol)
, вообще не нужно использоватьuseMemo 和 useCallback
используется только внутри компонента
object、array、函数等(没有作为 props 传递给子组件)
,且没有用到其他 Hook 的依赖数组中
, вообще не нужно использоватьuseMemo 和 useCallback
реальная сцена
Сценарий: есть родительский компонент, который содержит дочерние компоненты, и дочерний компонент получает функцию в качестве реквизита; обычно, если родительский компонент обновляется, дочерний компонент также выполняет обновление; но в большинстве сценариев обновление не требуется , вы можете использовать useCallback для возврата функции, а затем передать эту функцию дочернему компоненту в качестве реквизита; таким образом, дочерний компонент может избежать ненужных обновлений.
// 父组件
const Parent=()=>{
const [count, setCount] = useState(1);
const [val, setVal] = useState('');
const callback = useCallback(() => {
return count;
}, [count]);
return <div>
<h4>{count}</h4>
<Child callback={callback}/>
<div>
<button onClick={() => setCount(count + 1)}>+</button>
<input value={val} onChange={event => setVal(event.target.value)}/>
</div>
</div>;
}
// 子组件
const Child=({ callback })=>{
const [count, setCount] = useState(() => callback());
useEffect(() => {
setCount(callback());
}, [callback]);
return <div>
{count}
</div>
}
export default React.memo(Child) // 用React.memo包裹
Эта сцена повторно использует приведенный выше пример🌰, то есть
保持引用不变
, очевидно, что стоимость этого преимущества больше, чем стоимость оптимизации,子组件可以避免不必要的渲染