小知识,大挑战!本文正在参与“ 程序员必备小知识 ”创作活动
本文同时参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金
为什么要使用Hook
优化了类组件的三大问题
- 方便复用状态逻辑 类组件渲染属性(prop render)和高阶组件导致层级冗余,通过自定义hook可以很方便达到复用 1. HOC:固定的props可能被覆盖(重名) 2. render props: 无法在return语句外访问数据;用到多个时会嵌套地狱 3. hook:可以重命名;变量来源很清楚,方便定位问题;可以在return之外 使用数据;hook不会嵌套
- 副作用的关注点分离(一个hook单纯只做一件事,不像生命周期会混合(又是设置title,又获取接口))
副作用:数据渲染视图之外的功能,比如设置title,获取数据
- 函数组件无this问题
常用hook函数
useState
语法
const [position, setPosition] = useState({ x: 0, y: 0 })
useState参数为初始值,会返回数组,第一项为state值,第二项为更改state的方法
注意点
- 稳定顺序和数量
- useState不像setState会合并原有的state,比如
const [obj,setObj] = useState({like:0,switch:false})
//setObj({like:obj.like+1}) //错误的
setObj({like:obj.like+1,switch:obj.switch});
- 每个state声明个useState
const [like, setLike] = useState(0)
const [status, toggleStatus] = useState(false)
useEffect
语法
第一个参数为函数,如果有返回函数表示清除函数,第二个参数是个数组,表示执行时间(数组里任何一项更改时就会触发)
useEffect(() => {
document.title = `点击${like}次`
}, [like, status])
useEffect(() => {
window.addEventListener('resize',onResize,false);
return () => {
window.removeEventListener('resize',onResize)
}
},[]) //因为为空数组,永远不会改变,所以useEffect只会执行一次
注意点
- 先执行上一次的清除函数,再执行本次的副作用
自定义hook
语法
- 以use开头的函数
示例
自定义个hook实现loading
import { useState, useEffect } from 'react'
import axios from 'axios'
const useUrlLoader = (url: string, deps: any[] = []) => {
const [loading, setLoading] = useState(false)
const [data, setData] = useState<any>(null)
useEffect(() => {
setLoading(true)
axios.get(url).then((result) => {
setData(result.data)
setLoading(false)
})
}, deps)
return [data, loading]
}
export default useUrlLoader
interface IShowResult {
message: string
status: string
}
function App() {
const [
data,
loading
] = useURLLoader('https://dog.ceo/api/breeds/image/random', [show])
const dogResult = data as IShowResult
return (
<div className="App">
{/* {loading ? <p>读取中</p> : <img src={dogResult.message} />} */}
{/* 上面一行没加dogResult判空处理,是有问题的 */}
{loading ? <p>读取中</p> : <img src={dogResult && dogResult.message} />}
</div>
)
}
useMemo
要么就是存一个对象 prop,要么就是存一个 JSX
useRef
由于每次渲染周期获取到的数据都是本次的,而要达到跨渲染周期就需要采用useRef。另外要获取DOM节点或者子节点在函数里对应的ref方式就是useRef。useRef会在每次渲染时返回同一个 ref 对象
无法跨渲染周期
const LikeButton: React.FC = () => {
const [like, setLike] = useState(0)
function handleAlertClick() {
setTimeout(() => {
alert(`you clicked on ${like}`)
//形成闭包,所以弹出来的是当时触发函数时的like值
}, 3000)
}
return (
<>
<button onClick={() => setLike(like + 1)}>{like}赞</button>
<button onClick={handleAlertClick}>Alert</button>
</>
)
}
export default LikeButton
采用useRef修订(用法1:直接访问.current)
- 采用useRef定义
const likeRef = useRef(0)
- 由于每次渲染时返回同一个ref对象,所以通过设置以及获取likeRef.current
const LikeButton: React.FC = () => {
const [like, setLike] = useState(0)
// useRef 会在每次渲染时返回同一个 ref 对象
const likeRef = useRef(0)
function handleAlertClick() {
setTimeout(() => {
// 1.ref直接访问.current
alert(likeRef.current)
}, 3000)
}
return (
<>
<button
onClick={() => {
setLike(like + 1)
likeRef.current++
}}
>
{like}赞
</button>
<button onClick={handleAlertClick}>Alert</button>
</>
)
}
export default LikeButton
用法2:利用ref的全局性,区分出来是初始化还是更新
const didMountRef = useRef(false)
useEffect(() => {
// 2.利用ref的全局性,区分出来是初始化还是更新
if (didMountRef.current) {
console.info('this is updated')
} else {
didMountRef.current = true
}
})
用法3:访问DOM元素
const LikeButton: React.FC = () => {
const [like, setLike] = useState(0)
const domRef = useRef<HTMLParagraphElement>(null)
function handleAlertClick() {
setTimeout(() => {
if (domRef && domRef.current) {
//3.ref用在DOM上demo
alert(domRef.current.innerText)
//全局的,就是当前的like值 可以“跨渲染周期”保存数据
}
}, 3000)
}
return (
<>
<button onClick={() => setLike(like + 1)}>{like}赞</button>
<p ref={domRef}>{like}</p>
<button onClick={handleAlertClick}>Alert</button>
</>
)
}
export default LikeButton
useContext
useContext(MyContext)等价于类组件里的<MyContext.Consumer> 以及静态static contextType = MyContext
定义Provider
interface IThemeProps {
[key: string]: { color: string; background: string }
}
const thems: IThemeProps = {
light: {
color: '#000',
background: '#eee'
},
dark: {
color: '#fff',
background: '#222'
}
}
export const ThemeContxt = React.createContext(thems.light)
tsx部分
<ThemeContxt.Provider value={thems.dark}>
...
</ThemeContext.Provider>
定义Consumer
const LikeButton: React.FC = () => {
const [like] = useState(0)
const theme = useContext(ThemeContxt)
const style = {
color: theme.color,
background: theme.background
}
return <button style={style}>{like}赞</button>
}
export default LikeButton
useLayoutEffect与useEffect区别
- useEffect是在浏览器将所有变化渲染到屏幕上之后异步执行,而useLayoutEffect是在DOM变更之后同步执行
- 代码层面: useEffect是在beforeMutation调度,layout注册,commit之后异步执行; 而useLayoutEffect是在mutation执行destroy,在layout执行create
遇到问题
初始化个typescript React项目 当使用npx create-react-app react-with-typescript --typescript生成的项目不是typescript时,可以用npx create-react-app react-with-typescript --template typescript 试下