useState
1、定义
:useState是用来代替class Component里的state
2、useState的性质
-
useState的初始值只在第一次有效
,这要特别注意不然容易采坑,下面看个例子:import React, { useState,memo } from 'react' const Child =memo(({data})=>{ const [name,setName]=useState(data); return( <div> <div>Child</div> <div>{name}------{data}</div> </div> ) }) function Index(params) { const [count, setCount] = useState(0) const [name, setName] = useState('mike') return( <div> <div> {count} </div> <button onClick={()=>setCount(count+1)}>update count </button> <button onClick={()=>setName('jack')}>update name </button> <Child data={name}/> </div> ) } export default Index;
当我点击update name
时会更新父组件中的值同时name也会在子组件中显示,但是子组件中的name还是一开始的mike
,这说明useState只在初次渲染被使用一次。这里还要注意一点的就是如果你的初始state需要通过复杂的计算来获得,则可以传入一个函数,在函数中计算并返回初始state,此函数也只会在初次渲染时被调用。
-
每次渲染都是独立的闭包
:这句话怎么理解呢?简单点说就是每次渲染都有自己独立的Props、State和事件处理函数,这样就意味着每次更新状态的时候函数组件就会重新被调用,那么每次渲染就是独立的,取到的值不会受到后面操作的影响。
function Index(params) { let [count, setCount]=useState(0); function showCount(){ setTimeout(()=>{ console.log(count) },3000) } return ( <div> <div>{count}</div> <button onClick={()=>{setCount(count+1)}}>change count</button> <button onClick={showCount}>click show count</button> </div> ) }
3、useState和setState差异
:
在setState的时候,我们可以只修改state中的局部变量,而不需要将整个修改后的state传进去,举个例子:
//简单点我就直接写重点
this.state = {
count: 0,
age: 18,
}
handleClick = () => {
// 我们只需要传入修改的局部变量
this.setState({
count: 1
});
}
而使用useState后,我们修改state必须将整个修改后的state传入去,因为它会直接覆盖之前的state,而不是合并之前state对象。
const [data, setData] = useState({
count: 0,
name: 'cjg',
age: 18,
});
const handleClick = () => {
const { count } = data;
// 这里必须将完整的state对象传进去
setData({
...data,
count: count + 1,
})
};
useEffect
定义
:useEffect被称为副作用,指那些没有发生在数据向视图转化过程中的逻辑,比如ajax
请求、访问原生dom、本地持久化缓存、绑定/解绑事件、添加订阅,设置定时器等
使集集合
:
1、作为只在第一次使用的 componentDidMount ,用来异步请求数据
//将useEffect的第二个参数设置为[]
useEffect(()=>{
const list = fetch(...);
},[])
2、作为每次更新都会使用的 componentDidUpdate
//不设置useEffect的第二个参数
useEffect(()=>{
const list = fetch(...);
})
3、作为监听器去监听某个值或者某些值的变化,然后执行相应的操作。
//将userEffect的第二个参数设为一个数组,在这个数组中添加想要监听的值
useEffect(()=>{
const list = name
},[name])
4、作为组件消亡时调用的 componentWillUnmount ,这里可以去取消一些
//在useEffect的最后返回一个回调函数
useEffect(() => {
let timer=setTimeout()
return () => {
clearTimeout(timer)
}
},[])
为什么要取消订阅,因为每次render都会执行一次useEffect,正如我上面的例子,如果每次render都去设置一个定时器,这是一个很庞大的开销,所以每次render之前都要把上一次的定时器clear。
useEffect的潜规则
-
useEffect中每一次使用的state值都固定在useEffect内部,不会改变,除非useEffect刷新来获得最新的值。
const [count, setCount] = useState(0) useEffect(() => { console.log('use effect...',count) const timer = setInterval(() => { console.log('timer...count:', count) setCount(count + 1) }, 1000) return ()=> clearInterval(timer) },[])
-
useEffect不能被判断语句包裹
const [count, setCount] = useState(0) if(2 < 5){ useEffect(() => { .... }) }
useEffect不能被打断
useRef
1、useref返回一个可变的ref对象, 其 current
属性被初始化为传入的参数
const refContainer = useRef(initialValue);
useref返回的ref对象在组件的整个生命周期内保持不变,也就是说每次重新渲染函数组件时,返回的ref对象都是同一个(使用React.creatRef,每次重新渲染组件都会重新创建ref)再看看上面那个例子
const [count, setCount] = useState(0)
const countRef = useRef(0)
useEffect(() => {
console.log('use effect...',count)
const timer = setInterval(() => {
console.log('timer...count:', countRef.current)
setCount(++countRef.current)
}, 1000)
return ()=> clearInterval(timer)
},[])
这就能正常显示。
2、类组件、react元素用React.creatRef,函数式组件使用useRef来操作DOM
function Index(params) {
const [count, setCount] = useState(0);
const btnRef=useRef(null);
useEffect(()=>{
console.log("use effect");
console.log(btnRef.current)
const onClick=()=>{
setCount(count+1);
}
btnRef.current.addEventListener('click',onClick,false);
return ()=>{
btnRef.current.removeEventListener('click',onClick,false)
}
},[count])
return(
<div>
<div>
{count}
</div>
<button ref={btnRef}>click</button>
</div>
)
}
最后别忘了取消事件绑定,这样就可以获取到我们想要的DOM值,同时给相应的DOM添加事件。
memo
1、定义
:memo对标类组件中的PureComponent
,可以减少重新render的次数。
先看一个例子
function Child({name}){
console.log("this is Child");
return(
<div>{name}</div>
)
}
function Index(params) {
const [count,setCount]=useState(0);
return(
<div>
<div>{count}</div>
<button onClick={()=>{setCount(count+1)}}>click</button>
<Child name="小明"></Child>
</div>
)
}
首先第一次渲染会正常打印出"this is Child",然后我们点击click按钮改变count,这样父组件中的count就会更新,但是问题来了我子组件也会重新渲染,你可能会想传递给子组件的props没有变,要是子组件不重新渲染就好了,为什么会这么想呢?我们可以假设一下我们抽出去的子组件是一个非常庞大的组件,渲染一次会消耗很多性能,那么我们应该尽量减少这个组件的渲染,那么我们该怎么实现呢?答案就是memo
,在给定相同props的情况下渲染相同的结果,但是这里要注意一个问题就是memo是浅比较
,意思就是对象只会比较内存地址,只要内存地址没变,管你对象中的值怎么变化都不会触发render。
下面来包装一下上面那个例子。
const Child=memo(({name})=>{
console.log("this is Child");
return(
<div>{name}</div>
)
})
现在就实现了上面的功能。
memo高级用法:
默认情况下只会对props的对象进行浅层比较(浅层比较就是只会对比前后两次props对象的引用是否相同,不会对比对象里面的内容是否相同)如果想自己控制比较的过程那就需要自定义比较函数,通过第二个参数传入来实现,下面是一个简单的例子:
function areEqual(prevProps,nextProps){
return prevProps.data!==nextProps.data
}
const Child=memo((props)=>{
return(
<div>{props.data.count}</div>
)
},areEqual)
function Index(params) {
const [count,setCount]=useState(0);
const useCount=useRef({count:1})
const Countt=useRef(2)
useEffect(()=>{
let timer=setInterval(()=>{
setCount(++useCount.current.count)
},1000)
return ()=>{
clearInterval(timer)
}
},[])
return(
<div>
<div>{count}</div>
<button onClick={()=>{Countt.current++}}>click</button>
<Child data={useCount.current}></Child>
</div>
)
)
如果不加上判断函数,子组件就不会去更新,因为每次传入的对象地址没有变,但是对象中的数据发生改变。
useMemo
前面介绍的memo
是用来减少render次数,那接下来介绍的就是用来减少计算次数。
先看一个例子:
const Child=memo((props)=>{
console.log("this is Child")
return(
<div>{props.data.name}</div>
)
})
function Index(params) {
const [count,setCount]=useState(0);
const [name,setName]=useState("小红")
const data={
name
}
return(
<div>
<div>{count}</div>
<button onClick={()=>{setCount(count+1)}}>click</button>
<Child data={data}></Child>
</div>
)
}
这个例子和上面memo
讲的例子有点相似,这个例子中我们每点击一次父组件就会重新渲染一次,那么就会重新生成一个data
对象,这个对象和上次的对象地址是不一样的,所以子组件就检测到了,他发现你父组件传过来的对象地址不一样就会判断上一次和这一次的对象是不一样的然后就会刷新一次,但是这两次对象的值是一样的我再去重新渲染一次子组件实在是浪费性能,所以又该怎么解决呢?有人可能会结合上面那个例子,把这个对象定义在useRef
中,然后再去定义一个判断函数不就行了,这样是可以实现我们的功能,但是再想想有没有什么方法在父组件就给我们的对象做处理,如果传入子组件的对象没有改变就不去重新生成一个新的对象,useMemo
这个API就出现了,我们看看他是怎么工作的。
usememo
有着暂存的能力,会记住一些值,在重新渲染时会将依赖数组中的值取出来和上一次记录的值进行比较, 如果不相等才会重新执行回调函数,否则直接返回记住的值 ,对于这个例子没有新的对象就没有新的地址。
下面是改进后的:
const Child=memo((props)=>{
console.log("this is Child")
return(
<div>{props.data.name}</div>
)
})
function Index(params) {
const [count,setCount]=useState(0);
const [name,setName]=useState("小红")
const data=useMemo(()=>{
return {
name
}
},[name])
return(
<div>
<div>{count}</div>
<button onClick={()=>{setCount(count+1)}}>click</button>
<Child data={data}></Child>
</div>
)
}
最后要提醒一下,如果没有给usememo
提供依赖组,他就会在每次渲染时都会重新计算新的值。
useCallback
usememo
和useCallback
相似,都有着缓存的作用,但是他们本质的区别就在于一个是缓存值的,一个是缓存函数的,这里要注意一下useCallback
有没有后面的依赖很重要,如果没有依赖,每次渲染还是会重新生成新的函数。来看个例子:
const Child=memo((props)=>{
console.log("this is Child")
return(
<div>
<div>{props.name}</div>
<button onClick={props.click}>click child</button>
</div>
)
})
function Index(params) {
const [count,setCount]=useState(0);
const [age,setAge]=useState(12)
const callback=()=>{
setAge(age+1);
}
return(
<div>
<div>{count}</div>
<button onClick={()=>{setCount(count+1)}}>click</button>
<Child name="小明" click={callback}></Child>
</div>
)
}
我们来按操作一遍,页面首次渲染页面正常显示同时也会打印出this is Child
,当我们再次点击父组件中的按钮,父组件中的count改变,但是控制台又会打印一次this is Child
,说明子组件有重新渲染了一次,但是子组件却没有什么变化,也没有去触发子组件中的事件,说明这次渲染是多余的。那原因是什么呢?这其实我们呢上面也说过了,函数是组建每次渲染函数组件都会重头开始重新执行,那么这两次创建的callback函数肯定发生变化了,所以导致子组件重新渲染。
然后就进入主题了,使用我们的useCallback
方法,改进下代码
const Child=memo((props)=>{
console.log("this is Child")
return(
<div>
<div>{props.name}</div>
<button onClick={props.click}>click child</button>
</div>
)
})
function Index(params) {
const [count,setCount]=useState(0);
const [age,setAge]=useState(12)
const callback=useCallback(()=>{
setAge(age+1);
},[age])
return(
<div>
<div>{count}</div>
<button onClick={()=>{setCount(count+1)}}>click</button>
<Child name="小明" click={callback}></Child>
</div>
)
}