发布网友 发布时间:2024-10-02 20:26
共1个回答
热心网友 时间:2024-10-19 11:32
??注意:因为我们不涉及ClassComponent,所有内容的都会以FunctionComponent形式进行
在上一节中,我们学习了如何创建一个React项目,并了解到了JSX语法的本质就是JavaScript。希望你有完成我留下来的小作业~
本节,我们来学习在React中如何使用useState来支持页面动态渲染,以及使用需要注意的一些点,怎样避免这些坑。
在React中,如果你想要使用一个支持可操作的变量,像下面?这样写是不行的
functionApp(){letnum=0//声明一个变量numconstadd=()=>{//add函数用于每次点击btn时将num+1num++console.log(num)}return(<divstyle={{margin:100}}><divstyle={{marginBottom:16}}>{num}</div><buttononClick={add}>加1</button></div>)}我们在浏览器中打开
明明num被改变了,可为什么渲染的还是0呢?这还是因为,我们写的代码就是普通的JavaScript代码,上面的这段代码其实就等价于我们在原生JavaScript中这样写
letnum=0constel=document.getElmentById('root')el.innerHTML=numdocument.getElementById('btn').onclick=()=>{num++console.log(num)}试问,我们没有在每次num++以后将这个值更新到el的innerHTML中,页面怎么会展示呢?
一、使用useState我们使用着React,如果我们还需要每次num++以后来手动更新,那岂不是又回到了刀耕火种的时代了,所以React官方的APIuseState就出来了
useState是React上的一个方法,它接收一个默认值,并返回一个数组,数组包含两项,第一项是我们的数据,第二项是用来修改这个数据的函数。
functionApp(){const[num,setNum]=React.useState(0)//声明一个变量numconstadd=()=>{//add函数用于每次点击btn时将num+1setNum(num+1)console.log(num)}return(<div><div>{num}</div><buttononClick={add}>加1</button></div>)}我们在浏览器中看一下效果
芜湖~,页面如我们预期的更新成功了???
二、多次使用同一个setState我们有时可能会在一次事件中多次调用同一个setState
functionApp(){const[num,setNum]=React.useState(0)//声明一个变量numconstadd=()=>{setNum(num+1)console.log(num)setNum(num+2)console.log(num)}return(<div><div>{num}</div><buttononClick={add}>加1</button></div>)}也许我们想要的效果是先将num+1,接着再将num+2这样的效果,但是很抱歉的告诉你,这是不行的,生效的只会是最后一个**setState**,页面上只会渲染每次加2的结果,所以这里需要注意。那可能有的大聪明会说,那我业务当中就是会有这样的需求啊,就是可能会经过多次设置呢。
好吧,的确是有这样的需要的,React中提供了另外一种写法,很简单?
constadd=()=>{setNum(n=>n+1)//一些操作setNumn(n=>n+2)}这种写法是传入的就不是直接的值,而是一个function,它接受前面的state,我们可以根据这个stat来做一些处理以后,再返回一个state。所以我们这里调用完以后,页面上就会成功显示加3后的结果了。(但是一定要记住我说的:生效的只会是最后一个**setState**,如果你在最后仍写的是setNum(x),最后都会以x的值作为渲染)
三、使用useState存储对象类型数据使用useState存储对象类型的数据应该是很多刚开始使用React遇到的坑点了,比如我~
我们有一个对象,存放的是小明的信息,希望在每次点击按钮时,小明都能长大一岁(小明:wdnmd???
functionApp(){const[user,setUser]=React.useState({name:'小明',age:18})//每次点击都将年龄+1constplus=()=>{user.age+=1setUser(user)console.log('点击打印',user)//每次点击打印}console.log('render打印',user)//每次render都会打印return(<divstyle={{margin:100}}><divstyle={{marginBottom:16}}>姓名:{user.name}</div><divstyle={{marginBottom:16}}>年龄:{user.age}</div><buttononClick={plus}>长大一岁</button></div>)}exportdefaultApp;我们在浏览器中跑一下看下效果是否如我们所愿
我们发现,每次点击触发了,并且也更新了age,可是为什么没有触发render让页面更新呢?我们到源码中看一下怎么回事
constobjectIs=typeofObject.is==='function'?Object.is:is;functiondispatchSetState(fiber,queue,action){//省略......if(objectIs(eagerState,currentState)){//Fastpath.WecanbailoutwithoutschedulingReacttore-render.//It'sstillpossiblethatwe'llneedtorebasethisupdatelater,//ifthecomponentre-rendersforadifferentreasonandbythat//timethereducerhaschanged.return;}//省略......}其中eagerState就是我们想要更新渲染的值,currentState就是当前页面上渲染的值,而objectIs就是判断这两个值是否相等,如果相等,则直接退出更新。
而我们这里两个值都是同一个引用,自然也就无法触发React的后续操作了,所以每次点击时数据都更新了,但是页面就是没有反应。
同时,这意味着基本类型如果更新前后的值都一致的话objectIs也会判定为true,导致页面不会更新(重新render)。知道了是什么原因,那么我们就知道怎么解决了,不就是新给一个对象嘛,我改就完了,于是一顿操作如下?
//省略......//每次点击都将年龄+1constplus=()=>{setUser({age:user.age+1})console.log('点击打印',user)//每次点击打印}console.log('render打印',user)//每次render都会打印//省略......如果你是ts用户,并且开了代码检查,那肯定会发现报错了,提示你setUser中缺少参数name字段,如果你不是,页面会成功展示,并在你点击按钮时,会发现页面上的年龄虽然更新了,但是姓名不见了~
如果你是之前写Class方式的小伙伴,你肯定会觉得很疑惑。这是因为在hook中,每次修改的值都以传入的值来作为最后的值,已经不像之前写Class那般增量添加了~你必须这样写才可以保证字段不会被丢失?
//省略......//每次点击都将年龄+1constplus=()=>{setUser({...user,age:user.age+1})console.log('点击打印',user)//每次点击打印}console.log('render打印',user)//每次render都会打印//省略......也就是我们需要做一个浅拷贝,同时将想要改变的值添加进去,才会达到我们想要的目的?
总结下面我们进行技术总结:
useState接受一个初始值,它返回一个数组,里面包含两个值,第一个是拿来使用的值(state),第二个是用来更新这个值的函数(setState)
使用setState更新数据时,在它后面使用state还是未更新的值
如果想要setState依赖上一个setState的值,可以写一个函数,函数接收上一个state,并返回要设置的state,类似于setState(prevState=>newState)。
setState会将原本的值与传过来的值进行浅比较,如果前后两个值对比相等,则不进行渲染操作,直接return,所以对于Object类型的值在setState时需要进行一个浅拷贝操作。
希望能对你有所收获,如果你有兴趣从源码层面探索更多关于useState相关的知识,你可以看下一篇从源码层面来理解useState,不过这可能需要你具备一定的技术功底。当然你也可以选择粗略的过一下甚至直接跳过,这都不影响你学习React。(不过我还是建议你有精力的话可以粗略的过一下~)
原文:https://juejin.cn/post/7101860031804473381