双向绑定和useState的关系
下面是一个双向绑定的手写实现版本。
当我们使用useState的时候直接修改state是不会触发UI的重新渲染的,我们必须使用setState来更新state,因为setState会拦截我们的set操作并重新触发UI的渲染。React具体的做法是批量更新的,每次使用setState的时候会将当前组件标记为脏组件,并将状态放入到一个新的队列中,当当前执行栈为空的时候,批量处理队列中的状态,比较当前DOM和更新DOM之间的差异,进行批量更新。Vue的双向绑定是通过类似下面的方式来实现的,一方是通过获取绑定事件的数据,另一方面是通过数据劫持。
xml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>双向绑定</title>
</head>
<body>
<div>
<span id="display">12312</span>
</div>
<input id="input"/>
<script>
window.onload = () => {
// 双向绑定第一部分:UI改变引起数据改变
const span = document.getElementById("display");
const input = document.getElementById("input")
/*使用oninput浏览器事件捕获标签内容的变化然后赋值给我们的data数据 这样也就实现了UI改变引起数据的改变*/
input.oninput = function(e){
console.log(e.target.value)
// span.textContent=e.target.value
data.value=e.target.value
}
// 双向绑定第二部分:数据改变引起UI改变
/* 这里主要使用了数据劫持,通过使用Object.defineProperty 给我们要改变的数据重写get和set方法,我们需要在set方法中重新
实现更新的逻辑,主要是加上更新UI的操作这样我们在修改数据的时候UI也会同步修改。这和useState是一样的,当useState中的state
改变的时候setState也会有一个更新UI的操作,所以我们一般说useState会触发UI的重新渲染。*/
let data = {
data:""
}
function defineReactive(obj,key,value){
let internalValue = value;
const getter = ()=> internalValue
const setter =(newVal)=>{
internalValue=newVal;
updateUI()
}
Object.defineProperty(obj,key,{
get:getter,
set:setter
})
}
defineReactive(data,'value','')
function updateUI(){
input.value=data.value
span.textContent=data.value
}
setInterval(() => {
data.value = new Date(); // 这会自动更新UI
}, 1000);
}
</script>
</body>
</html>
setState每次都会重新渲染UI吗
这是不一定的,如果我每次set的都是同一个值,值没有变化,那就不会更新UI,比如setCount(2)
,这就是说我每次都将count设置为2,重复设置不会更新UI。
只改变对象的属性值不会触发重新渲染UI,具体的代码可以参看下面:
javascript
function DiyPage01 (){
const [person,setPerson] = useState({name:"猪八戒",age:3000})
const changePerson = function(){
person.name="孙悟空"
setPerson(person)
}
return (
<>
<div>这是diy01页面</div>
<div>{person.name}--{person.age}</div>
<Button onClick={changePerson}>按钮</Button>
</>
)
}
如果想要改变state之后重新渲染UI,可以用下面的方法:
浅拷贝
ini
const changePerson = function(){
person.name="孙悟空"
const person2 =Object.assign({},person) //浅拷贝
setPerson(person2)
}
当然ES6中的解构复制也可以帮我们完成浅拷贝,代码如下:
php
const changePerson = function(){
setPerson({...person,name:"孙悟空"})
}
setState异步更新
scss
//点击按钮count只会+1,因为三个setCount中的count并非最新的count
const changeCounter = function(){
/*设置count的时候如果需要用到旧的count的时候需要注意,
我们的修改可以失效,因为下面的多个count都是同一个count,
并不是使用上次更新完毕的count*/
setCount(count+1)
setCount(count+1)
setCount(count+1)
}
//点击下面的按钮会+3,回调函数的形参是React传递的当前最新的count
const changeCounter = function(){
/* 当我们传入一个回调函数的时候,React会将最新的state值作为参数传递给回调函数,
所以回调函数里面拿到的count是最新的*/
setCount((count)=>count+1)
setCount((count)=>count+1)
setCount((count)=>count+1)
}
上面三次count+1最终count是1而不是3的原因如下:
- 在
changeCounter
函数内部,count
是一个闭包变量 ,它的值在函数执行时就已经确定了(即当前的count
值)。- 即使你调用
setCount(count + 1)
三次,每次的count
都是同一个初始值 ,而不是前一次setCount
更新后的值。
useRef相对于自定义的对象做了什么优化
我们在使用useRef可以将一个DoM元素绑定到我们创建的useRef变量上,这样我们就可以通过useRef变量来操作DOM。除此之外我们还可以使用一个自定义的对象,并将这个对象绑定在DOM上。但是自己创建的对象会在每次渲染的时候重新新建对象并赋值,这样就有一定的性能开销。使用useRef可以避免这种开销。
csharp
// 方法一:页面渲染不会改变divRef的值
const divRef = useRef();
// 方法二:重新渲染UI的时候我们创建的对象都要重新重建然后赋值 divRef每次都是一个新值
const divRef = {current:null}