React Hooks

在React 的世界中,有容器组件和UI组件之分,在React Hooks出现之前,UI组件我们可以使用函数,无状态组件来展示UI,而对于容器组件,函数组件就显得无能为力,我们依赖于类组件来获取数据,处理数据,并向下传递参数给UI组件进行渲染。在我看来,使用React Hooks相比于从前的类组件有以下几点好处:

1.代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者不利于维护和迭代,通过React Hooks可以将功能代码聚合,方便阅读维护

2.组件树层级变浅,在原本的代码中,我们经常使用HOC/render props等方式来复用组件的状态,增强功能等,无疑增加了组件树层数及渲染,而在React Hooks中,这些功能都可以通过强大的自定义的Hooks来实现

React 在v16.8的版本中推出了React Hooks新特性,虽然社区还没有最佳实践如何基于React Hooks来打造复杂应用(至少我还没有),凭借着阅读社区中大量的关于这方面的文章,下面我将通过十个案例来帮助你认识理解并可以熟练运用React Hooks大部分特性。

1、useState保存组件状态

在类组件中,我们使用this.state来保存组件状态,并对其修改触发组件重新渲染。比如下面这个简单的计数器组件,很好诠释了类组件如何运行:

function UseState(props) {
    
    let [count, setCount] = useState(0)

    function hideClick() {
        setCount(() => {
            return count + 1
        })
    }

    return (
        <div>
            <span>{count}</span>
            <button onClick={hideClick}>++</button>
        </div>
    )
}

export default UseState

通过传入useState参数后返回一个带有默认状态和改变状态函数的数组。通过传入新状态给函数来改变原本的状态值。值得注意的是useState不帮助你处理状态,相较于setState非覆盖式更新状态,useState覆盖式更新状态,需要开发者自己处理逻辑。(代码如上)

似乎有个useState后,函数组件也可以拥有自己的状态了,但仅仅是这样完全不够。

2、useEffect处理副作用

函数组件能保存状态,但是对于异步请求,副作用的操作还是无能为力,所以React提供了useEffect 来帮助开发者处理函数组件的副作用,在介绍新API之前,我们先来看看类组件是怎么做的:

import React from 'react'

import { useState, useEffect } from 'react'

export default function (props) {
    let [data, setData] = useState({ count: 0 })

    useEffect(() => {
        console.log('effect')
    }, [data])
    //[]  空数组不依赖任何值,只执行一次

    useEffect(() => {
        return () => {
            console.log('unmount')
        }
    }, [])

    return (
        <div>
            <div>{data.count}</div>
            <button onClick={() => setData(data => ({ count: data.count + 1 }))}>click</button>
        </div>
    )
}

3、UseContext减少组件层级

通过React Context的语法,在App组件中可以跨过Foo组件给Bar组件传递数据,而在React Hookes中,我们可以使用useContext进行改造。

import React, { createContext, useContext } from 'react'

let nameContext = createContext({ name: 'zhangsan' })
let ageContext = createContext({ age: 12 })

export default function UseContext() {

    let name = useContext(nameContext)
    let age = useContext(ageContext)
    
    return (
        <div>
            {name.name} {age.age}
        </div>
    )
}

4、useReducer

useReducer这个Hooks在使用上几乎跟Redux、React-Redux几乎一样,唯一缺少的就是无法使用redux提供的中间件。

import React, { useReducer } from 'react'

const initState = {
    count: 0
}

const reducer = (state, action) => {
    switch (action.type) {
        case 'add':
            return {
                count: state.count + 1
            }
        case 'minus':
            return {
                count: state.count - 1
            }
        default:
            return state
    }
}

export default function UseReducer() {
    let [state, dispatch] = useReducer(reducer, initState);

    return (
        <div>
            <button onClick={() => dispatch({ type: 'minus' })}>-</button>
            <span>{state.count}</span>
            <button onClick={() => dispatch({ type: 'add' })}>+</button>
        </div>
    )
}

5、useCallback 记忆函数

一版把函数式组件理解为class组件render函数的语法糖,所以每次重新渲染的时候,函数式组件内部所有的代码都会重新执行一遍。所以上述代码中每次render, handleClick都会是一个新的引用,所以也就是说传递给SomeComponent组件的props.onClick一直在变(因为每次都是一个新的引用),所以才会说这种情况下,函数组件在每次渲染的时候如果有传递函数的话都会重渲染子组件。

而有了useCallback 就不一样了,你可以通过useCallback 获得一个记忆后的函数。

function(){
    const memoizedHandleClicj = useCallback(()=>{
        console.log('Click happend')
    },[]); //空数组代表无论什么情况下该函数都不会发生改变
    return <SomeComponent onclick={memoizedHandleclick}>click Me</SomeComponent>;
}

第二个参数传入一个数组,数组中的每一项一旦值或者引用发生改变,useCalback就会重新返回一个新的函数提供给后面进行渲染。

这样质押子组件继承了PureComent或者使用Reac.memo就可以有效避免不必要的VDOM渲染。

import React, { useState, useCallback, memo } from 'react'

const Child = memo(function (props) {
    console.log('Child run....')
    return (
        <>
            <h1>Hello</h1>
            <button onClick={props.onAdd}>add</button>
        </>
    )
}, () => {
    //true false自己定义是否缓存
    return true
})

export default function UseCallback() {
    console.log('UseCallback run....')
    let [count, setCount] = useState(0)

    const handleAdd = useCallback(
        () => {
            console.log('added.')
        }, [])

    return (
        <div>
            <div>{count}</div>
            <Child onAdd={handleAdd}></Child>
            <button onClick={() => { setCount(100) }}>change count</button>
        </div>
    )
}

6、useMemo 记忆组件

useCallback的功能完全可以由useMemo所取代,如果你想通过使用useMemo返回一个记忆函数也是完全可以的。

usecallback(fn,inputs) is equivalent to useMemo( ( ) => fn,inputs ).

所以前面使用useCallback 的例子可以使用useMemo进行改写:

function App(){
    const memoHandleClick = useMeno(()=> ()=>{
        console.log('click')
    },[]) //空数组代表无论什么情况下该函数都不会发生改变
    return <SomeComponent onclick={memoHandleClick}>Click me<SomeComponent/>
}

唯一的区别是: useCallback 不会执行第一个参数函数,而是将它返回给你,而useMemo会执行第一个函数并且将函数执行结果返回给你。所以在前面的例子中,可以返回handleClick 来达到存储函数的目的。

所以useCallback 常用记忆事件函数,生成记忆后的事件函数并传递给子组件使用。而useMemo更适合经过函数计算得到一个确定的值,比如记忆组件。

import React, { useState, useMemo, memo } from 'react';

const ChildHood = function (props) {
    return (
        <div>Chind Hood</div>
    )
}

const Child = memo(function ({ a, b, onAdd }) {
    console.log('Child run....')

    const Child1 = useMemo(() => () => <ChildHood a={a}></ChildHood>, [a])
    const Child2 = useMemo(() => () => <ChildHood b={b}></ChildHood>, [b])

    return (
        <>
            <h1>Hello</h1>
            <button onClick={onAdd}>add</button>
            <Child1></Child1>
            <Child2></Child2>
        </>
    )
})

7、useRef保存引用值

import React, { createRef, useRef } from 'react'

export default function UseRef() {

    // const btn = createRef();
    const btn = useRef();

    function handleClick() {
        console.log(btn)
    }

    return (
        <div>
            <button ref={btn} onClick={handleClick}>click</button>
        </div>
    )
}
export default function App() {
  const count = useRef(0)

  const handleClick = (num) => {
    count.current += num
    setTimeout(() => {
      console.log("count: " + count.current);
    }, 3000)
  }

  return (
    <div>
      <p>You clicked {count.current} times</p>
      <button onClick={() => handleClick(1)}>增加 count</button>
      <button onClick={() => handleClick(-1)}>减少 count</button>
    </div>
  );
}

8、useImperativeHandle

import React, { useRef, useEffect, useImperativeHandle, forwardRef } from "react";

function ChildInputComponent(props, ref) {
    const inputRef = useRef(null);
    useImperativeHandle(ref, () => inputRef.current);
    return <input type="text" name="child input" ref={inputRef} />;
}

//forwardRef 高阶组件  ref传到下个组件
const ChildInput = forwardRef(ChildInputComponent);

function App() {
    const inputRef = useRef(null);
    useEffect(() => {
        inputRef.current.focus();
    }, []);
    return (
        <div>
            <ChildInput ref={inputRef} />
        </div>
    );
}

export default App

通过这种方式,App组件可以获得子组件的input的DOM节点。

9、useLayoutEffect同步执行副作用

大部门情况下,使用useEffect就可以处理组件的副作用

10、useEffect和useLayoutEffect有什么区别?

简单来说就是调用时机不同,useLayoutEffect和原来componentDidMount & componentDidupdate一致,在react完成DOM更新后马上同步**调用的代码,会阻塞页面渲染。而'useEffect是会在整个页面渲染完才会调用的代码。

在实际使用时如果想避免页面抖动(在useEffect里修改DOM很有可能出现)的话,可以把需要操作DOM的代码放在useLayoutEffect里。关于使用useEffect导致页面抖动。

不过useLayoutEffect在服务端渲染时会出现一个warning,要消除的话得用useBffect代替或者推迟渲染时机。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值