useState
作用:数据存储,派发更新。
使得react无状态组件能够像有状态组件一样,可以拥有自己state。
类组件中,会在constructor里面定义状态this.state,使用setState来更新。函数组件就可以使用useState来创建状态。
使用方法:
接受一个参数作为初始值。 返回一个数组,第一个值为状态,第二个值为改变状态的函数。
import React,{ useState } from 'react'
function StateFunction () {
const [name, setName] = useState('函数')
return (
<div onClick={ () => setName('我使用hooks变成这样了') }>
{name}
</div>
)
}
export default StateFunction
useEffect
在函数组件中,当组件完成挂载、dom渲染完成之后,使用useEffect做一些操纵dom、请求数据的操作。
数据获取,数据订阅,以及手动更改 React 组件中的 DOM 都属于副作用。 因为渲染出的页面都是静态的,任何在其之后的操作都会对他产生影响,所以称之为副作用。
使用方法:
1.第一个参数,接收一个函数作为参数
2.第二个参数,接收【依赖列表】,只有依赖更新时,才会执行函数
3.返回一个函数,先执行返回函数,再执行参数函数
如果不接受第二个参数,则在第一次渲染完成之后和每次更新渲染页面的时候,都会调用useEffect的回调函数。
如果第二个参数是空数组[ ],相当于没有发生变化,useEffect不依赖于state、props中的任意值,只会在首次渲染完成的时候调用一次,可以用来获取页面初始数据。
如果第二个参数是一个包含一个或多个值的数组,如[num]、[num,val],那么当数组中的值(任意一项即可)改变时,都会重新触发回调函数。注意这个值的改变是浅比较。
使用useEffect函数来模拟生命周期函数
componentDidMount
会在组件挂载后(插入 DOM 树中)立即调用。该阶段通常进行以下操作:
- 执行依赖于DOM的操作;
- 发送网络请求;(官方建议)
- 添加订阅消息(会在componentWillUnmount取消订阅);
可用useEffect传入空数组模拟,只在第一次渲染后执行。
useEffect( () => {console.log('第一次渲染')},[])
componentDidUpdate
componentDidUpdate() 会在更新后会被立即调用,首次渲染不会执行此方法。
该阶段通常进行以下操作:
- 当组件更新后,对 DOM 进行操作;
- 如果对更新前后的 props 进行了比较,也可以选择在此处进行网络请求;(例如,当 props 未发生变化时,则不会执行网络请求)。 该方法有三个参数:
- prevProps: 更新前的props
- prevState: 更新前的state
- snapshot: getSnapshotBeforeUpdate()生命周期的返回值
useEffect传入数组即可模拟。每次数组中的值发生改变,就会更新渲染。
useEffect( () => {console.log('n变了,更新渲染')},[n])
componentWillUnmount
componentWillUnmount() 会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作:
- 清除 timer,取消网络请求或清除
- 取消在 componentDidMount() 中创建的订阅等;
useEffect里面return一个函数会在销毁的时候执行。
useEffect( () => {
console.log('第一次渲染')
//return出来的函数本来就是更新前、销毁前执行的函数,现在不监听任何状态,所以只在销毁前执行
return () => {
console.log('组件销毁')
}
})
如果我们需要在组件销毁的阶段,做一些取消dom监听,清除定时器等操作,那么我们可以在useEffect函数第一个参数的结尾返回一个函数,用于清除这些副作用。相当于componentWillUnmount。
useEffect(()=>{
/* 定时器 延时器等 */
const timer = setInterval(()=>console.log(666),1000)
/* 事件监听 */
window.addEventListener('resize', handleResize)
/* 此函数用于清除副作用 */
return function(){
clearInterval(timer)
window.removeEventListener('resize', handleResize)
}
},[ a ])
注意:当useEffect返回一个函数时,会先执行返回函数,再执行参数函数!
useEffect( () => {
console.log('参数函数')
const updateMouse = (e) => {
console.log('打印当前位置')
setPositions({ x:e.clientX, y:e.clientY })
}
document.addEventListener('click',updateMouse) // 添加绑定方法事件(要修改依赖,绑定到依赖上)
return () => {
// 在每次执行useEffect之前都会执行上一次return中内容
document.removeEventListener('click',updateMouse)
// 移除绑定方法事件(要修改依赖,绑定到依赖上)
console.log('组件销毁')
}
})
首次进入页面会先执行除return以外的内容,也就是将updateMouse方法绑定到click事件上。接着将这一次useEffect中的清除事件监听removeEventListener返回出去,但是此时并没有执行return中的内容。
第一次点击的时候,打印当前鼠标页面坐标,然后执行上一次return返回出去的内容,也就是执行上一次return中的清除事件监听的方法,清除了上一个useEffect中的绑定事件。
然后再开始执行新的useEffect中的绑定事件方法,并再次将该次useEffect清除事件绑定的方法return返回出去,如此就形成了一个链式一样的过程。
当页面卸载的时候,会执行最后一次return返回出来的清除事件绑定的方法,这样也就保证了页面卸载的时候,移除了绑定添加的DOM事件方法。
另外,useEffect中定义的函数的执行不会阻碍浏览器更新视图,也就是说这些函数是
异步执行的,而componentDidMonut和componentDidUpdate中的代码都是同步执行的。
useLayoutEffect
渲染更新之前的 useEffect
使用方法和useEffect一样,只是执行时机不同。
useEffect执行顺序: 组件更新挂载完成 -> 浏览器dom 绘制完成 -> 执行useEffect回调
useLayoutEffect执行顺序: 组件更新挂载完成 -> 执行useLayoutEffect回调-> 浏览器dom 绘制完成
useLayoutEffect 代码可能会阻塞浏览器的绘制。
useMemo
性能优化
形成独立的渲染空间,能够使组件,变量按照约定好规则更新。渲染条件依赖于第二个参数【依赖数组】。无状态组件的更新是从头到尾的更新,如果想要重新渲染一部分视图,而不是整个组件,那么useMemo是最佳方案,避免了不需要的更新和不必要的上下文执行。
类组件使用ShouldcomponentUpdate来限制更新次数,memo就像无状态组件的ShouldcomponentUpdate,memo函数针对的是一个组件的渲染,是否重复执行。(<Foo/>)
useMemo更为细小,针对的是一段函数逻辑,是否重复执行。(() => {})
memo的作用结合了pureComponent纯组件和 ShouldcomponentUpdate功能,会对传进来的props进行一次对比,然后根据第二个函数返回值来进一步判断哪些props需要更新。 memo包裹的组件,给该组件加了限制更新的条件,是否更新取决于memo第二个参数返回的boolean值。
useMemo和memo差不多,都是判定是否满足当前的限定条件来决定是否执行useMemo的callback函数,而useMemo的第二个参数是一个依赖数组,数组里的参数变化决定了useMemo是否更新回调函数,useMemo返回值就是经过判定更新的结果。它可以应用在元素上,应用在组件上,也可以应用在上下文当中。
被useMemo包裹起来的上下文,形成一个独立的闭包,会缓存之前的state值,如果没有加相关的更新条件,是获取不到更新之后的state的值的。
使用方法
1.第一个参数,接收一个函数作为参数
2.第二个参数,接收【依赖列表】,只有依赖更新时,才会执行函数
3.返回的是一个值。返回值可以是任何,函数、对象等都可以
useMemo与useEffect的不同就是调用时机 —— useEffect执行的是副作用,所以一定是在渲染之后运行的;而useMemo是需要有返回值的,返回值会参与渲染,所以useMemo是是在渲染期间完成的。
import React, { useMemo } from 'react'
useMemo(() => {}, [] )
应用场景
优化需要计算的场景
import React, { useState, useMemo } from 'react';
function App() {
const [count, setCount] = useState(0);
const double = useMemo(() => {
return count * 2
}, [count === 3])
return (
<div>
<button onClick={() => setCount(count + 1)}>点我加一</button>
<h1>count: {count}</h1>
<h1>double: {double}</h1>
</div>
)
}
export default App;
这里是否重新渲染Double组件是根据表达式count === 3来判断的。
count === 3值为true或者false,当从false变为true或者从true变为false时,double值会更新渲染。
所以不断点击按钮,count和double的变化如下:
count:0 double:0
count:1 double:0
count:2 double:0
count:3 double:6
count:4 double:8
count:5 double:8
父子组件的重新渲染
使用useMemo(() => { return onClick }, [])来包裹onClick函数,让它不会每一次都重新渲染,而是只渲染一次。
只有当App组件里的count值为3和4的时候,Counter组件才重新渲染。
import React, { useState, useMemo, memo } from 'react';
const Counter = memo(function Counter(props) {
console.log('Counter');
return (
<h1>Counter: {props.counter}</h1>
)
})
function App() {
const [count, setCount] = useState(0);
const double = useMemo(() => {
return count * 2
}, [count === 3])
const onClick = useMemo(() => {
return () => {
console.log('click')
}
}, [])
return (
<div>
<button onClick={() => setCount(count + 1)}>点我加一</button>
<h1 onClick={props.onClick}>count: {count}</h1>
<Counter counter={double} onClick={onClick}/>
</div>
)
}
export default App;
useCallback
useCallback就是useMemo可以省略一层函数的写法。
useMemo(() => fn, []) 就等于 useCallback(fn, [])。
const onClick = useMemo(() => {
return () => {
console.log('click')
}
}, [])
const onClick = useCallback(() => {
console.log('click')
}, [])
useMemo 和 useCallback 接收的参数都是一样,第一个参数为回调,第二个参数为要依赖的数据。
共同作用:仅仅 依赖数据 发生变化, 才会重新计算结果,也就是起到缓存的作用。
两者区别:
- useMemo 计算结果是 return 回来的值, 主要用于缓存计算结果的值,应用场景如: 需要计算的状态
- useCallback 计算结果是函数, 主要用于缓存函数,应用场景如: 需要缓存的函数,因为函数式组件每次任何一个 state 的变化,整个组件都会被重新刷新,一些函数是没有必要被重新刷新的,此时就应该缓存起来,提高性能,减少资源浪费。
应用场景
一般父组件更新,子组件也会更新。但是如果父组件传入子组件的内容不变,那么子组件某些操作(某些操作是指需要跟随传入内容的改变而同步进行的操作)是没必要执行的,这会影响页面性能,所以我们可以对这情况进行优化。
直接用上面的例子:
import React, { useState, useMemo, memo, useCallback } from 'react';
const Counter = memo(function Counter(props) {
console.log('Counter');
return (
<h1 onClick={props.onClick}>Counter: {props.counter}</h1>
)
})
function App() {
const [count, setCount] = useState(0);
const double = useMemo(() => {
return count * 2
}, [count === 3])
const onClick = useCallback(() => {
console.log('click')
}, [])
return (
<div>
<button onClick={() => setCount(count + 1)}>点我加一</button>
<h1>count: {count}</h1>
<Counter counter={double} onClick={onClick}/>
</div>
)
}
export default App;
如果有函数传递给子组件,使用useCallback
如果有值传递给子组件,使用useMemo
分割线~~~~~~~~~
整理不完了,后续再弄