本文共 7073 字,大约阅读时间需要 23 分钟。
Hooks 的 API 可以参照 React 官网。本文主要是结合 Demo 详细讲解如何用 Hooks 来实现 React Class Component 写法,让大家更深的理解 Hooks 的机制并且更快的入门。注意:Rax 的写法和 React 是一致的,本文 Demo 基于 React 实现,
本文内容包括如下: 一、在 Hooks 中如何实现 Class Component 生命周期 二、在 Hooks 中如何实现 shouldComponentUpdate 三、在 Hooks 中如何实现 this 四、在 Hooks 中如何获取上一次值 五、在 Hooks 中如何实现父组件调用子组件方法 六、在 Hooks 中如何获取父组件获取子组件的 dom 节点
Hooks 的出现其实在弱化生命周期的概念,官网也讲解了原先的生命周期在写法上有哪些弊端,这里不做优缺点的比较,只给大家做写法转换。
Hooks 生命周期主要是借助 useEffect
和 useState
来实现,请看如下 Demo
Class Component constructor 函数只会在组件实例化时调用一次,而且会在所有生命周期函数调用之前调用
useState 传入初始化函数 fn 只会执行一次,并且执行时机在 render 之前
function useConstruct(fn) { useState(fn);}复制代码
依赖项给空数组,只会执行一次
// componentDidMountfunction useDidMount(fn) { useEffect(fn, []);}复制代码
依赖项不传值,任何触发的 render 都会执行
// componentDidUpdatefunction useDidUpdate(fn) { useEffect(fn);}复制代码
// componentWillUnmountfunction useUnMount(fn) { useEffect(() => fn, []);}复制代码
生命周期详细 Demo 如下
import React, { useState, useEffect, useRef } from 'react';// constructfunction useConstruct(fn) { // useState 传入初始化函数 fn 只会执行一次 useState(fn);}// componentDidMountfunction useDidMount(fn) { // 依赖项给空数组,只会执行一次 useEffect(fn, []);}// componentDidUpdatefunction useDidUpdate(fn) { // 依赖项不传值,任何触发的 render 都会执行 useEffect(fn);}// componentWillUnmountfunction useUnMount(fn) { useEffect(() => fn, []);}function Block() { const [count, setCount] = useState(0); const instance = useRef({}); useConstruct(() => { instance.current.name = 1; console.log('Block Component----Construct'); }); useDidMount(() => { console.log('Block Component----componentDidMount'); }); useDidUpdate(() => { console.log('instance.current.name', instance.current.name); console.log('Block Component----componentDidUpdate'); }); useUnMount(() => { console.log('Block Component----componentWillUnmount'); }); console.log('Block render'); return ();}export default function ParentComp() { const [unMountBlock, setUnMountBlock] = useState(false); return (Block组件
count: {count}
);}复制代码unMountBlock: {unMountBlock?'true':'false'}
{!unMountBlock ?: null}
通过 useMemo
来实现 shouldComponentUpdate
,依赖项填写当前组件依赖的 props,useMemo
内部对依赖项进行浅比较,其中的任何一个依赖项变化时,重新 render 组件。 与 Class Component 不同的是,比较操作在组件外部。
import React, { useState, useMemo } from 'react';function Counter(props) { console.log('Counter render'); return ();}function Time(props) { console.log('Time render'); return (count: {props.count}
);}export default function Demo() { const [count, setCount] = useState(0); const [time, setTime] = useState(0); const [count2, setCount2] = useState(10); // 用于实现 shouldComponentUpdate // 与 Class Component 不同点:当前是在组件外做比较 const child1 = useMemo(() =>time: {props.time}
, [count]); const child2 = useMemo(() => , [time]); return ( );}复制代码count: {count}
time: {time}
count2: {count2}
{child1} {child2}
首先你要明白 Hooks 实际上仍然是 Function Component 类型,它是没有类似于 Class Component 的 this 实例的。
通过使用 useRef
来模拟实现,internalRef.current
可以认为是当前的 this 变量,用来绑定相关变量
import React, { useEffect, useRef } from 'react';export default function useThis() { // internalRef.current 默认值为 {} const internalRef = useRef({}); // internalRef.current 类似于 this 变量 const self = internalRef.current; if (self.didMount) { console.log('componentDidMount', self.didMount); } useEffect(() => { self.didMount = true; }, []); return ();}复制代码如何使用this 变量
借助 useEffect
和 useRef
的能力来保存上一次值
import React, { useState, useRef, useEffect } from 'react';function usePrevious(value) { const ref = useRef(); useEffect(() => { ref.current = value; }); return ref.current;}export default function Counter() { const [count, setCount] = useState(0); const previousCount = usePrevious(count); return ();}复制代码count: {count}
previousCount: {previousCount}
上节已经说到,Hooks 实际上仍然是 Function Component 类型,它本身是不能通过使用 ref 来获取组件实例的,所以在 Hooks 中想要实现 父组件调用子组件的方法,需要两个 API来配合使用,即forwardRef
和useImperativeHandle
。在子组件中使用 useImperativeHandle
来导出方法,并使用 forwardRef
包裹组件, 在父组件中使用 useRef
传递 ref 给子组件。
import React, { useRef, useImperativeHandle, forwardRef } from 'react';const TextInput = forwardRef((props, ref) => { const inputRef = useRef(null); const handleFocus = () => { inputRef.current.focus(); }; // 暴露方法给外部组件调用 // useImperativeHandle 应当与 forwardRef 一起使用 useImperativeHandle(ref, () => ({ focusInput: handleFocus, hello: ()=>{} })); return ();});export default function Parent() { const inputRef = useRef(null); const handleFocus = () => { console.log(typeof findDOMNode) console.log(inputRef.current) // 调用子组件方法 inputRef.current.focusInput(); }; return ();}复制代码
findDOMNode
用于找到组件的dom节点,用于相关的 dom 处理操作。
import React, { useRef, useImperativeHandle, forwardRef } from 'react';import {findDOMNode} from 'react-dom';const TextInput = forwardRef((props, ref) => { return ();});export default function Parent() { const inputRef = useRef(null); const handleFindDom = () => { const node = findDOMNode(inputRef.current); }; return ();}复制代码
这里可能有人会提出疑问,在 Class Component 里面 ref 可以取到组件 dom 的同时,也可以取到组件实例方法,为何这里要拆分成 三、四 两个章节来讲? 很遗憾,在 Hooks 里面无法通过一个 ref 同时实现两个功能,只能通过规范的方式来使用,比如:
import React, { useRef, useImperativeHandle, forwardRef } from 'react';import {findDOMNode} from 'react-dom';const TextInput = forwardRef((props, ref) => { const inputRef = useRef(null); const compRef = useRef(null); const handleFocus = () => { inputRef.current.focus(); }; useImperativeHandle(ref, () => ({ // 导出方法 focusInput: handleFocus, // 导出当前 dom 节点 compRef: compRef })); return (作者:JSCON简时空 链接:https://juejin.im/post/5daece165188256b030ad79e 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。);});export default function Parent() { const inputRef = useRef(null); const handleFocus = () => { // 获取 TextInput 组件的 dom 节点 const node = findDOMNode(inputRef.current.compRef.current); console.log(node); // 调用 TextInput 组件方法 inputRef.current.focusInput(); }; return ();}