一、useRef
useRef返回一个ref对象,返回的ref对象再组件的整个生命周期保持不变。
最常用的ref是两种用法:
- 用法一:引入DOM(或者组件,但是需要是class组件)元素;
案例一:引用DOM
import React, {useRef} from "react";class TestCpn extends React.Component{render() {return <h2>TestCpn</h2>}
}function TestCpn2(props) {return <h2>TestCpn2</h2>
}export default function RefHookDemo01() {const titleRef = useRef()const inputRef = useRef()const testRef = useRef()const testRef2 = useRef()function changeDOM() {titleRef.current.innerHTML = 'hello world'inputRef.current.focus()console.log(testRef.current)console.log(testRef2.current)}return (<div><h2 ref={titleRef}>RefHookDemo01</h2><input type="text" ref={inputRef}/><TestCpn ref={testRef} /><TestCpn2 ref={testRef2} /><button onClick={e => changeDOM()}>修改DOM</button></div>)
}
- 用法二:保存一个数据,这个对象在整个生命周期中可以保存不变;
案例二:使用ref保存上一次的某一个值
import React, {useEffect, useRef, useState} from "react";export default function RefHookDemo02() {const [count, setCount] = useState(0)const numRef = useRef(count)useEffect(() => {numRef.current = count}, [count])return (<div>{/*<h2>numRef中的值: {numRef.current}</h2>*/}{/*<h2>count中的值: {count}</h2>*/}<h2>count上一次的值:{numRef.current}</h2><h2>count当前的值:{count}</h2><button onClick={e => setCount(count + 10)}>+10</button></div>)
}
二、useImperativeHandle
useImperativeHandle并不是特别好理解,我们一点点来学习。
我们先来回顾一下ref和forwardRef结合使用:
- 通过forwardRef可以将ref转发到子组件;
- 子组件拿到父组件中创建的ref,绑定到自己的某一个元素中;
import React, {forwardRef, useRef} from "react";const HYInput = forwardRef((props,ref) => {return <input ref={ref} type="text"/>}
)export default function ForwardRefDemo() {const inputRef = useRef()return (<div><HYInput ref={inputRef}/><button onClick={e => inputRef.current.focus()}>聚焦</button></div>)
}
forwardRef的做法本身没有什么问题,但是我们是将子组件的DOM直接暴露给了父组件:
- 直接暴露给父组件带来的问题是某些情况的不可控;
- 父组件可以拿到DOM后进行任意的操作;
- 但是,事实上在上面的案例中,我们只是希望父组件可以操作的focus,其他并不希望它随意操作;
通过useImperativeHandle可以只暴露固定的操作:
- 通过useImperativeHandle的Hook,将传入的ref和useImperativeHandle第二个参数返回的对象绑定到了一起;
- 所以在父组件中,使用 inputRef.current时,实际上使用的是返回的对象;
- 比如我调用了 focus函数;
import React, {forwardRef, useImperativeHandle, useRef} from "react";const HYInput = forwardRef((props, ref) => {const inputRef = useRef()useImperativeHandle(ref, () => {return {focus: () => {inputRef.current.focus()console.log('useImperativeHandle中回调函数返回的对象里面的focus')}}}, [inputRef.current])return <input ref={inputRef} type="text"/>}
)export default function ForwardRefDemo02() {const inputRef = useRef()return (<div><HYInput ref={inputRef}/><button onClick={e => inputRef.current.focus()}>聚焦</button></div>)
}
三、useLayoutEffect
useLayoutEffect看起来和useEffect非常的相似,事实上他们也只有一点区别而已:
- useEffect会在渲染的内容更新到DOM上后执行,不会阻塞DOM的更新;
- useLayoutEffect会在渲染的内容更新到DOM上之前执行,会阻塞DOM的更新;
如果我们希望在某些操作发生之后再更新DOM,那么应该将这个操作放到useLayoutEffect。
案例: useEffect和useLayoutEffect的对比
四、自定义Hook
自定义Hook本质上只是一种函数代码逻辑的抽取,严格意义上来说,它本身并不算React的特性。
需求0:所有的组件在创建和销毁时都进行打印
- 组件被创建:打印 组件被创建了;
- 组件被销毁:打印 组件被销毁了;
import React, {useEffect} from "react";
const Home = (props) => {useEffect(() => {console.log('Home组件被创建出来了~')return () => {console.log('Home组件被销毁了!')}}, [])return <h2>Home</h2>
}
const Profile = (props) => {useEffect(() => {console.log('Profile组件被创建出来了~')return () => {console.log('Profile组件被销毁了!')}}, [])return <h2>Profile</h2>
}
export default function CustomHookLifeDemo01() {useEffect(() => {console.log('CustomHookLifeDemo01组件被创建出来了~')return () => {console.log('CustomHookLifeDemo01组件被销毁了!')}}, [])return (<div><h2>CustomHookLifeDemo01</h2><Home /><Profile /></div>)
}
import React, {useEffect} from "react";
const Home = (props) => {useLoggingLife('Home')return <h2>Home</h2>
}
const Profile = (props) => {useLoggingLife('Profile')return <h2>Profile</h2>
}
export default function CustomHookLifeDemo01() {useLoggingLife('CustomHookLifeDemo01')return (<div><h2>CustomHookLifeDemo01</h2><Home /><Profile /></div>)
}
function useLoggingLife(name) {useEffect(() => {console.log(`${name}组件被创建出来了~`)return () => {console.log(`${name}组件被销毁了!`)}}, [])
}
需求一:Context的共享
import {useContext} from "react";
import {TokenContext, UserContext} from "../App";function useUserContext() {const user = useContext(UserContext)const token = useContext(TokenContext)return [user, token]
}export default useUserContext
需求二:获取鼠标滚动位置
需求三:localStorage数据存储