1.useState
1.什么是hooks:一套能够使函数组件更强大,更灵活的钩子
// React体系里组件分为类组件和函数组件
// 经过多年的实战,函数组件是一个更加匹配React的设计理念UI = f(data)
// 也更有利于逻辑拆分与重用的组件表达形式,而先前的函数组件是不可以由自己的状态
// 所以从reactV16.8,Hooks应运而生
// 注意:
// 1.有了hooks之后,为了兼容老版本,class类组件并没有被移除,俩者都可以使用
// 2.有了Hooks之后,不能在把函数称为无状态组件了,因为hooks为函数组件提供了状态
// 3.hooks只能用在函数组件中使用
// hooks解决问题
// 1.组件的状态逻辑复用
// 2.class组件自身的问题
// 基础使用 useState
// 1.导入useState函数 react
// 2.执行这个函数并且传人初始值 必须在函数组件中
// 3.【数据,修改数据方法】
// 4.使用数据 修改数据
import { useState } from 'react'
function App() {
// count:数据状态
// setCount:修改count状态的方法(专用函数)
const [ count, setCount ] = useState(0)
return (
<div>
<div>count: { count }</div>
<div onClick={ () => setCount((count) => count + 1) }>click count</div>
</div>
)
}
export default App;
2.hooksEffect
// hooks useEffect
import { useState, useEffect } from 'react'
function App () {
// 组件首次渲染
// 首次被渲染的时候,组件内部的代码会被执行一次
// 其中useState也会跟着执行 这里重点注意 初始值只在首次渲染时生效
// 更新渲染 setCount都会更新
// 1.app组件会再次渲染 这个函数会再次执行
// 2.useState再次执行 得到的新的count值不是0而是修改之后的1 模板会用新值渲染
// 重要一句话:useState初始值只在首次渲染生效,后续只要调用setCount整个app代码都会执行
// 此时的count每次拿到的都是最新值
// count:数据状态
// setCount:修改count状态的方法(专用函数)
const [count, setCount] = useState(0)
const [list, setList] = useState([1, 2, 3])
// 注意事项:1.只能出现在函数组件中 2.不能嵌套在if/for/其它函数中(react按照hooks的调用顺序识别每一个hook)
// useEffect
// 1.理解函数副作用:副作用时相对于主作用来说的,一个函数除了主作用,其他的作用就是副作用
// 主作用是根据数据(state/props)渲染UI,除此之外都是副作用(如:手动修改DOM)
// 常见的副作用:1.1 数据请求ajax发送 1.2 手动修改dom 1.3 localstorage操作
// useEffect函数的作用就是为react函数组件提供副作用处理
// 基本使用
// 组件初始化时会执行一次,每次组件更新时都会触发一次
// 第二个参数表示依赖项,空数组(组件初始化时会执行一次,更新不会执行)
// 数组中有依赖项[count](依赖特定项,初始化执行一次,依赖项发生改变时会触发)
useEffect(() => {
console.log('执行了')
document.title = count
}, [count])
// 三种用法
useEffect(() => {
// 这种方式使用只会初始化渲染一次
// 相当于vue的生命钩子
}, [])
useEffect(() => {
// 这种方式使用初始化渲染一次,count值修改也会触发一次
}, [count])
useEffect(() => {
// 这种方式使用初始化渲染一次,所有useState定义的值修改都会触发一次
})
return (
<div>
<div>count: {count}</div>
<button onClick={() => { setCount((count) => count + 1); setList(list => [...list, 2, 3, 4]) }}>click count</button>
{list.map((item, index) => (<div key={index}>{item}</div>))}
<button onClick={() => setList(list => [4, 5, 6])}>click fun</button>
</div>
)
}
export default App
1. useCallback & useMemo
useCallback应用:
import React, { useState, useCallback } from 'react';
import Button from './Button';
export default function App() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const [count3, setCount3] = useState(0);
const handleClickButton1 = () => {
setCount1(count1 + 1);
};
const handleClickButton2 = useCallback(() => {
setCount2(count2 + 1);
}, [count2]);
return (
<div>
<div>
<Button onClickButton={handleClickButton1}>Button1</Button>
</div>
<div>
<Button onClickButton={handleClickButton2}>Button2</Button>
</div>
<div>
<Button
onClickButton={() => {
setCount3(count3 + 1);
}}
>
Button3
</Button>
</div>
</div>
);
}
// Button.jsx
import React from 'react';
const Button = ({ onClickButton, children }) => {
return (
<>
<button onClick={onClickButton}>{children}</button>
<span>{Math.random()}</span>
</>
);
};
export default React.memo(Button);
在案例中可以分别点击Demo中的几个按钮来查看效果:
- 点击 Button1 的时候只会更新 Button1 和 Button3 后面的内容;
- 点击 Button2 会将三个按钮后的内容都更新;
- 点击 Button3 的也是只更新 Button1 和 Button3 后面的内容。
上述效果仔细理一理就可以发现,只有经过 useCallback 优化后的 Button2 是点击自身时才会变更,其他的两个只要父组件更新后都会变更(这里Button1 和 Button3 其实是一样的,无非就是函数换了个地方写)。下面我们仔细看看具体的优化逻辑。
这里或许会注意到 Button 组件的 React.memo 这个方法,此方法内会对 props 做一个浅层比较,如果如果 props 没有发生改变,则不会重新渲染此组件。
const a = () => {};
const b = () => {};
a === b; // false
上述代码可以看到我们两个一样的函数却是不相等的(这是个废话,我相信能看到这的人都知道,所以不做解释了)。
const [count1, setCount1] = useState(0);
// ...
const handleClickButton1 = () => {
setCount1(count1 + 1);
};
// ...
return <Button onClickButton={handleClickButton1}>Button1</Button>
回头再看上面的 Button 组件都需要一个 onClickButton 的 props ,尽管组件内部有用 React.memo 来做优化,但是我们声明的 handleClickButton1 是直接定义了一个方法,这也就导致只要是父组件重新渲染(状态或者props更新)就会导致这里声明出一个新的方法,新的方法和旧的方法尽管长的一样,但是依旧是两个不同的对象,React.memo 对比后发现对象 props 改变,就重新渲染了。
const handleClickButton2 = useCallback(() => {
setCount2(count2 + 1);
}, [count2]);
上述代码我们的方法使用 useCallback 包装了一层,并且后面还传入了一个 [count2] 变量,这里 useCallback 就会根据 count2 是否发生变化,从而决定是否返回一个新的函数,函数内部作用域也随之更新。
由于我们的这个方法只依赖了 count2 这个变量,而且 count2只在点击 Button2 后才会更新 handleClickButton2,所以就导致了我们点击 Button1 不重新渲染 Button2 的内容。。
import React, { useState, useCallback } from 'react';
import Button from './Button';
export default function App() {
const [count2, setCount2] = useState(0);
const handleClickButton2 = useCallback(() => {
setCount2(count2 + 1);
}, []);
return (
<Button
count={count2}
onClickButton={handleClickButton2}
>Button2</Button>
);
}
我们调整了一下代码,将 useCallback 依赖的第二个参数变成了一个空的数组,这也就意味着这个方法没有依赖值,将不会被更新。且由于 JS 的静态作用域导致此函数内 count2 永远都 0。
可以点击多次 Button2 查看变化,会发现 Button2 后面的值只会改变一次。因为上述函数内的 count2 永远都是 0,就意味着每次都是 0 + 1,Button 所接受的 count props,也只会从 0 变成 1且一直都将是 1,而且 handleClickButton2 也因没有依赖项不会返回新的方法,就导致 Button 组件只会因 count 改变而更新一次。
上述提到的是不更新所带来的问题,接下来在看一个频繁更新所带来的问题。
const [text, setText] = useState('');
const handleSubmit = useCallback(() => {
// ...
}, [text]);
return (
<form>
<input value={text} onChange={(e) => setText(e.target.value)} />
<OtherForm onSubmit={handleSubmit} />
</form>
);
上述例子中可以看到我们的 handleSubmit 会依赖 text 的更新而去更新,在 input 的使用中 text 的变化肯定是相当频繁的,假如这时候我们的 OtherForm 是一个很大的组件,必须要进行优化这个时候可以使用 useRef 来帮忙。
const textRef = useRef('');
const [text, setText] = useState('');
const handleSubmit = useCallback(() => {
console.log(textRef.current);
// ...
}, [textRef]);
return (
<form>
<input value={text} onChange={(e) => {
const { value } = e.target;
setText(value)
textRef.current = value;
}} />
<OtherForm onSubmit={handleSubmit} />
</form>
);
使用 useRef 可以生成一个变量让其在组件每个生命周期内都能访问到,且 handleSubmit 并不会因为 text 的更新而更新,也就不会让 OtherForm 多次渲染。
但是: 不要把所有的方法都包上 useCallback
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const handleClickButton1 = () => {
setCount1(count1 + 1)
};
const handleClickButton2 = useCallback(() => {
setCount2(count2 + 1)
}, [count2]);
return (
<>
<button onClick={handleClickButton1}>button1</button>
<button onClick={handleClickButton2}>button2</button>
</>
)
上面这种写法在当前组件重新渲染时会声明一个新的 handleClickButton1 函数,下面 useCallback 里面的函数也会声明一个新的函数,被传入到 useCallback 中,尽管这个函数有可能因为 inputs 没有发生改变不会被返回到 handleClickButton2 变量上。
那么在我们这种情况它返回新的函数和老的函数也都一样,因为下面 已经都会被渲染一下,反而使用 useCallback 后每次执行到这里内部要要比对 inputs 是否变化,还有存一下之前的函数,消耗更大了。
useCallback 是要配合子组件的 shouldComponentUpdate 或者 React.memo 一起来使用的,否则就是反向优化