目前好的推销网站,建筑工程施工合同电子版,学院网站建设报告,王野天个人简历什么是Hooks ref引用值
普通变量的改变一般是不好触发函数组件的渲染的#xff0c;如果想让一般的数据也可以得到状态的保存#xff0c;可以使用ref
import { useState ,useRef} from reactfunction App() {const [count, setCount] useState(0)let num useRef(0)const h…什么是Hooks ref引用值
普通变量的改变一般是不好触发函数组件的渲染的如果想让一般的数据也可以得到状态的保存可以使用ref
import { useState ,useRef} from reactfunction App() {const [count, setCount] useState(0)let num useRef(0)const handleClick () {setCount(count 1)num.current //current,当前值console.log(num.current)}return (divbutton onClick{handleClick}计数/button/div)
}
export default App那你就要问了这和useState有什么区别 更改时不会触发渲染例如这里我们注释掉setCount的语句
import { useState ,useRef} from reactfunction App() {const [count, setCount] useState(0)let num useRef(0)const handleClick () {// setCount(count 1)num.current //current,当前值console.log(num.current)}return (divbutton onClick{handleClick}计数/button{count},{num.current}/div)
}
export default App可以看出num的值变了但是并没有渲染出来 所以我们一般不会把ref写在jsx的结构里但是可以在渲染过程之外随意修改和更新current的值不需要像useState一样使用里面的方法修改的值才有效
如果我们在里面开启定时器在这里一秒触发一次如果单击按钮num的值一秒增加一次如果点击按钮多次就会同时开启多个定时器数值就会涨的飞快
import { set } from lodash
import { useState ,useRef} from reactfunction App() {const [count, setCount] useState(0)let timernulllet num useRef(0)const handleClick () {setCount(count 1)setInterval(() {console.log(123)}, 1000);}return (divbutton onClick{handleClick}计数/button{count},{num.current}/div)
}
export default App一般的解决办法是每次新开一个定时器就关掉旧定时器
import { set } from lodash
import { useState ,useRef} from reactfunction App() {const [count, setCount] useState(0)let timernullconst handleClick () {clearInterval(timer)timersetInterval(() {console.log(123)}, 1000);}return (divbutton onClick{handleClick}计数/button{count}/div)
}
export default App但是如果对整个函数重新调用也就是启用useState就无法销毁定时器了
因为整个函数重新调用定时器是在上一次调用产生的这一次删不掉上一次的定时器作用域不同
import { set } from lodash
import { useState ,useRef} from reactfunction App() {const [count, setCount] useState(0)let timernullconst handleClick () {setCount(count 1)clearInterval(timer)timersetInterval(() {console.log(123)}, 1000);}return (divbutton onClick{handleClick}计数/button{count}/div)
}
export default App这时候就需要对timer做记忆也就是使用ref就算走了多次渲染函数也可以销毁
import { set } from lodash
import { useState, useRef } from reactfunction App() {const [count, setCount] useState(0)let timer useRef(null)const handleClick () {setCount(count 1)clearInterval(timer.current)timer.current setInterval(() {console.log(123)}, 1000)}return (divbutton onClick{handleClick}计数/button{count}/div)
}
export default App试了试如果timer放全局定义是可以的但是感觉这样不利于函数的封装性
import { useState, useRef } from reactlet timer null
function App() {const [count, setCount] useState(0)const handleClick () {setCount(count 1)clearInterval(timer)timer setInterval(() {console.log(123)}, 1000)}return (divbutton onClick{handleClick}计数/button{count}/div)
}
export default App通过ref操作原生dom
react里可以使用js的方法操作dom例如什么querySelector这种css选择器但是更推荐我们使用React的方法操作dom
import { set } from lodash
import { useState, useRef } from reactfunction App() {const myRef useRef(null)const handleClick () {//通过ref操作原生domconsole.log(myRef.current.innerHTML)myRef.current.style.background red}return (divbutton onClick{handleClick}计数/buttondiv ref{myRef}hello React/div/div)
}
export default App这么写是会报错的钩子是按顺序使用的在循环里这么写会报错所以这么写不符合规范而且也不建议把const myRefuseRef(null)写在结构里
import { set } from lodash
import { useState, useRef } from reactfunction App() {const list [{ id: 1, text: aaa },{ id: 2, text: bbb },{ id: 3, text: ccc },]const handleClick () {//通过ref操作原生domconsole.log(myRef.current.innerHTML)myRef.current.style.background red}return (div{list.map((item) {const myRef useRef(null)return li key{item.id} ref{myRef}{item.text}/li})}/div)
}export default App
在循环操作里ref可以使用回调函数的写法
import { useState, useRef } from reactfunction App() {const list [{ id: 1, text: aaa },{ id: 2, text: bbb },{ id: 3, text: ccc },]return (divul{list.map((item) {return (likey{item.id}ref{(myRef) {myRef.style.background red}}{item.text}/li)})}/ul/div)
}export default App但是我报了很多错说是 其实就算style在卸载的时候为空加个逻辑判断就好了
铭记励志轩
import { useState, useRef } from reactfunction App() {const list [{ id: 1, text: aaa },{ id: 2, text: bbb },{ id: 3, text: ccc },]return (divul{list.map((item) {return (likey{item.id}ref{(myRef) {if (myRef) {myRef.style.background red}}}{item.text}/li)})}/ul/div)
}export default App组件设置ref需要forwardRef进行转发
forwardRef 是 React 提供的一个高阶函数用于将 ref 从父组件传递到子组件中的 DOM 元素或组件实例。它主要用于解决在函数组件中直接使用 ref 时无法访问子组件内部 DOM 元素的问题。
在 MyInput 组件中ref 被绑定到 input 元素也就是把ref转发到内部的input身上然后在 handleClick 函数中ref.current 用于直接操作 input 元素
import { useRef ,forwardRef} from reactconst MyInput forwardRef(function MyInput(props, ref) {return (input typetext ref{ref}/input
)
})function App() {const ref useRef(null)const handleClick () {ref.current.focus()ref.current.style.backgroundred}return (divbutton onClick{handleClick}点我/buttonMyInput ref{ref} //div)
}export default AppuseImperativeHandle自定义ref 你想要它其中两个方法focus 和 scrollIntoView。为此用单独额外的 ref 来指向真实的浏览器 DOM。然后使用 useImperativeHandle 来暴露一个句柄它只返回你想要父组件去调用的方法 import { useRef ,forwardRef,useImperativeHandle} from reactconst MyInput forwardRef(function MyInput(props, ref) {const inputRefuseRef(null)useImperativeHandle(ref,(){return{focus(){inputRef.current.focus()}}})return (input typetext ref{inputRef}/input
)
})function App() {const ref useRef(null)const handleClick () {
ref.current.focus()}return (divbutton onClick{handleClick}点我/buttonMyInput ref{ref} //div)
}export default App
点击按钮获取焦点 然后你就要问了因为我也想问这个获取焦点的操作为什么要写成这样
这样会更加灵活不用完全写在dom里可以把你想要写的功能放在useImperativeHandle的第二个参数里也就是里面的回调函数来实现你自己的功能、自定义的功能
相当于你自己写了一个组件可以像普通的组件使用在里面添加属性和属性值你也可以写自己的组件实现的功能
例如这个button的属性是他自己自带的我们写的组件也可以写一些自己带的属性focus 和 scrollIntoView就是这个作用 button onClick{handleClick}点我/button
比如我们再添加一个获取焦点以后背景变红色的功能 import { useRef ,forwardRef,useImperativeHandle} from reactconst MyInput forwardRef(function MyInput(props, ref) {const inputRefuseRef(null)useImperativeHandle(ref, () {return {focus() {inputRef.current.focus()},focusAndStyle() {inputRef.current.focus()inputRef.current.style.background red}}})return (input typetext ref{inputRef}/input
)
})function App() {const ref useRef(null)const handleClick () {if (ref) {ref.current.focusAndStyle()}}return (divbutton onClick{handleClick}点我/buttonMyInput ref{ref} //div)
}export default App 纯函数如何处理副作用
纯函数之前提过只关注输入和输出、两次执行函数是否结果一样axios 上面图的意思是组件是纯函数但是有时候组件不得不写一些违背纯函数的功能例如Ajax调用、还有我们用ref操作dom等都是副作用
事件本身可以去除副作用
import { useRef, forwardRef, useImperativeHandle } from reactfunction App() {const ref useRef(null)//副作用不符合纯函数的规范// setTimeout(() {// ref.current.focus()// },1000)//副作用但是符合纯函数的规范因为事件可以处理副作用const handleClick () {ref.current.focus()}return (divbutton onClick{handleClick}点击/buttoninput typetext ref{ref} //div)
}export default App但不是所有的副作用都可以用事件去除而且有时候需要初始化副作用
所以在react里时间操作可以处理副作用比如useEffect钩子
import { useRef, forwardRef, useImperativeHandle, useEffect } from reactfunction App() {const ref useRef(null)//副作用不符合纯函数的规范// setTimeout(() {// ref.current.focus()// },1000)//副作用但是符合纯函数的规范因为事件可以处理副作用// const handleClick () {// ref.current.focus()// }//可以在初始的时候进行副作用操作//useEffect触发的时机是jsx渲染后触发的这样就会消除副作用的影响useEffect(() {if (ref) {ref.current.focus()}})return (divbutton onClick{handleClick}点我/buttoninput typetext ref{ref} //div)
}export default App一刷新就自动获取焦点
初次渲染和更新渲染都会触发useEffect(),因为每次渲染jsx以后会触发useEffect()整个当前函数组件作用域的最后时机触发的
import {useState,useEffect} from reactfunction App() {const [count, setCount] useState(0) useEffect(() {console.log(count)})const handleClick () {setCount(count 1)}return (divbutton onClick{handleClick}点我/button/div)
}
export default App 执行顺序
组件首次渲染调用 useState(0)初始化 count 为 0渲染组件返回jsx-点击按钮触发handleClick函数调用useCount-重新渲染组件-执行useEffect中的副作用函数
useEffect的依赖项使用
一个组件里可能有多个副作用原则上来说多个副作用可以放在同一个useEffect里但是比较杂
可以使用useEffect的依赖项进行拆解因为useEffect本身就是个函数 我们可以使用多个useEffect来控制副作用
import {useState,useEffect} from reactfunction App() {const [count, setCount] useState(0) const [msg, setMsg] useState(hello React)useEffect(() {console.log(msg)})useEffect(() {console.log(count)})const handleClick () {setCount(count 1)}return (divbutton onClick{handleClick}点我/button/div)
}
export default App
两个根一个一样也可以正常的更新、渲染 但是有时候根据不同的应用场景有些副作用需要重新触发有的不需要可以指定哪些可以触发、哪些不可以。
添加useEffect的依赖项目 import {useState,useEffect} from reactfunction App() {const [count, setCount] useState(0) const [msg, setMsg] useState(hello React)useEffect(() {console.log(msg)},[msg])useEffect(() {console.log(count)},[count])//这就是依赖项const handleClick () {setCount(count 1)}return (divbutton onClick{handleClick}点我/button/div)
}
export default App
初始都触发更新以后只有对应依赖项发生改变时才触发像这里msg没改变所以不触发 内部是怎么判别有没有发生变化的是通过Object.is()方法来判定是否改变 像这样依赖项是静态的空数组只有初始会触发以后发生改变都不会渲染他 不过尽量不要这么写尽量还是要把里面用到的状态写在依赖项里
除了状态变量还有props、计算变量等也可以写在依赖项里 计算变量是指通过其他变量或数据动态计算得出的值而不是直接存储的静态值。计算变量通常用于根据某些条件或逻辑动态生成数据而不是手动维护这些数据。 函数也可以做依赖项
函数也可能成为计算变量所以也可以作为依赖项
但是会出现一个问题Object.is()方法在判别函数的时候会看引用类型的地址两个函数是两个函数内存地址不一样Object.is()就会判别为false useCallBack可以修复这个问题可以缓存函数一般用于组件性能优化其他情况不推荐使用这里先不细说
最好的解决办法是把函数定义在useEffect内部
import {useState,useEffect} from reactfunction App() {const [count, setCount] useState(0) useEffect(() {const foo () {console.log(count)}foo()},[count])//这就是依赖项return (div/div)
}
export default App
useEffect清理操作
先写一个简易的关闭聊天室的功能 import {useState,useEffect} from reactfunction Chat() {return (div我是聊天室/div)
}function App() {const [show,setShow] useState(true)const handleClick () {setShow(false)}return (divbutton onClick{handleClick}点我关闭聊天室/button{showChat/}/div)
}
export default App
useEffect可以在卸载组件的时候清理
import {useState,useEffect} from reactfunction Chat() {useEffect(() { console.log(进入聊天室)//useEffect返回一个函数这个函数会在组件卸载的时候执行return () {console.log(离开聊天室)}})return (div我是聊天室/div)
}function App() {const [show,setShow] useState(true)const handleClick () {setShow(false)}return (divbutton onClick{handleClick}点我关闭聊天室/button{showChat/}/div)
}
export default App 增加了useEffect更新时的清理功能清理功能是整个作用域的结束而不是下一次作用域的开始
import {useState,useEffect} from reactfunction Chat({title}) {useEffect(() { console.log(进入,title)//useEffect返回一个函数这个函数会在组件卸载的时候执行return () {console.log(离开,title)}},[title])return (div我是课堂/div)
}function App() {const [show, setShow] useState(true)const [title,setTitle]useState(电磁场与电磁波)const handleClick () {setShow(false)}const handleChange (e) {setTitle(e.target.value)}return (divbutton onClick{handleClick}点我退出课堂/buttonselect value{title} onChange{handleChange}option value电磁场与电磁波电磁场与电磁波/optionoption value半导体物理半导体物理/option/select{show Chat title{title} /}/div)
}
export default App 清理功能是很常见的需求如果注释掉清理的代码就会变成这样
没有退出只有进入如果是严格模式其实会把函数执行两边可以看到 实际上是违背正常逻辑的如果加上清理功能在严格模式执行的现象应该是
也就是说严格模式也可以起到提醒你需要自检的作用
再举一个栗子如果我们要切换不同的课程切换不同的课程耗时不同
import {useState,useEffect} from reactfunction fetchChat(title) {const delay title电磁场与电磁波?2000:1000return new Promise((resolve,reject){setTimeout(() {resolve([{id:1,text:title1},{id:2,text:title2},{id:3,text:title3}])}, delay);})
}function Chat({ title }) {const [list,setList]useState([])useEffect(() { fetchChat(title).then((data) {setList(data)
})return () {}},[title])return (div{list.map((item){return li key{item.id}{ item.text}/li})}/div)
}function App() {const [show, setShow] useState(true)const [title,setTitle]useState(电磁场与电磁波)const handleClick () {setShow(false)}const handleChange (e) {setTitle(e.target.value)}return (divbutton onClick{handleClick}点我退出课堂/buttonselect value{title} onChange{handleChange}option value电磁场与电磁波电磁场与电磁波/optionoption value半导体物理半导体物理/option/select{show Chat title{title} /}/div)
}
export default App
如果不添加useEffect二者耗时不同就会出现上一个还在加载下一个作用域已经执行的问题
变成这样 加入清理就可以解决问题
import {useState,useEffect} from reactfunction fetchChat(title) {const delay title电磁场与电磁波?2000:1000return new Promise((resolve,reject){setTimeout(() {resolve([{id:1,text:title1},{id:2,text:title2},{id:3,text:title3}])}, delay);})
}function Chat({ title }) {const [list, setList] useState([])useEffect(() { let ignore falsefetchChat(title).then((data) {if (!ignore) {setList(data)//上一次没结束不能提前更新}
})return () {ignoretrue//做清理工作}},[title])return (div{list.map((item){return li key{item.id}{ item.text}/li})}/div)
}function App() {const [show, setShow] useState(true)const [title,setTitle]useState(电磁场与电磁波)const handleClick () {setShow(false)}const handleChange (e) {setTitle(e.target.value)}return (divbutton onClick{handleClick}点我退出课堂/buttonselect value{title} onChange{handleChange}option value电磁场与电磁波电磁场与电磁波/optionoption value半导体物理半导体物理/option/select{show Chat title{title} /}/div)
}
export default App 有很多人在遇到这样的异步操作时会用async和await但是React不支持你这么写 你可以单独写一个async函数然后把要执行的放进去
import { useState, useEffect } from react;function fetchChat(title) {const delay title 电磁场与电磁波 ? 2000 : 1000;return new Promise((resolve, reject) {setTimeout(() {resolve([{ id: 1, text: title 1 },{ id: 2, text: title 2 },{ id: 3, text: title 3 }]);}, delay);});
}function Chat({ title }) {const [list, setList] useState([]);useEffect(() {let ignore false;const fetchData async () {const data await fetchChat(title);if (!ignore) {setList(data); // 上一次没结束不能提前更新}};fetchData();return () {ignore true; // 做清理工作};}, [title]);return (div{list.map((item) (li key{item.id}{item.text}/li))}/div);
}function App() {const [show, setShow] useState(true);const [title, setTitle] useState(电磁场与电磁波);const handleClick () {setShow(false);};const handleChange (e) {setTitle(e.target.value);};return (divbutton onClick{handleClick}点我退出课堂/buttonselect value{title} onChange{handleChange}option value电磁场与电磁波电磁场与电磁波/optionoption value半导体物理半导体物理/option/select{show Chat title{title} /}/div);
}export default App;
useEffectEvent
实验性版本提供的钩子 如果你的状态变量有多个只想执行一个可以用实验版本的useEffectEvent
介于六月份同学说实验版本也用不了所以不讲了