React Hooks

1) what is Hooks?

之前也有函数式组件,但是没有状态,无法保存数据,所以一直用类式组件

class MyCount extends Component {
    state = {
        count: 0,
    }

    componentDidMount() {
        this.interval = setInterval(() => {
            this.setState({ count: this.state.count + 1 })
        }, 1000)
    }

    componentWillUnmount() {
        if (this.interval) {
            clearInterval(this.interval)
        }
    }

    render() {
        return <span>{this.state.count}</span>
    }
}
  • 引入Hooks函数,重写上述组件
function MyCountFunc() {
    const [count, setCount] = useState(0)

    useEffect(() => {
        const interval = setInterval(() => {
            setCount(x => x + 1)
        }, 1000)

        return () => clearInterval(interval)
    })

    return <span>{count}</span>
}
  1. setCount代替之前 this.setState的功能,修改state数据,其实也是reducer的功能,useState也是useReducer实现的
  2. useEffect实现 componentDidMount 的功能;return 的回调函数实现 componentWillUnmount 的功能

2) State Hooks

  1. useState -- 值类型,每次传入的都是新的值
function MyCountFunc() {
    const [count, setCount] = useState(0)

    // setCount两种用法
    // setCount(value)
    // setCount(callback)    

    useEffect(() => {
        const interval = setInterval(() => {
            // setCount(count + 1)  值固定为1,不会变化
            setCount(x => x + 1)
        }, 1000)

        return () => clearInterval(interval)
    }, [])

    return <span>{count}</span>
}
  1. useReducer -- 引用类型

    如果state是一个对象,每次要求传递的state是一个新的对象,而且这个对象比较复杂,就不能像useState中的函数那样来修改,否则可能实现不了修改的目的(setCount(count + 1));就像redux中 Object.assign()、JSON.parse(JSON.stringify())

function countReducer(state, action) {
    switch(action.type) {
        case 'add':
            return state + 1
        case 'minus':
            return state - 1
        default:
            return state
    }
}

function MyCountFunc() {
    const [count, dispatchCount] = useReducer(countReducer, 0)

    useEffect(() => {
        const interval = setInterval(() => {
            dispatchCount({ type: 'minus' })
        }, 1000)

        return () => clearInterval(interval)
    }, [])

    return <span>{count}</span>
}

3) Effect Hook

function MyCountFunc() {
    const [count, dispatchCount] = useReducer(countReducer, 0)
    const [name, setName] = useState('firm')

    /*
     * 不添加 dependencies,每次组件内的 state(这里就是count、name)变化,都会update component
     *      1. 所以第一次Mount Component,会输出 'effect invoked'
     *      2. 之后每次Update Component,就会先执行上一次状态中useEffect的return 回调函数      =>      'effect deteched'   =>      再执行这一次状态中的useEffect   =>  'effect invoked'
     *      3. 切换组件时,Unmount Component,就只有    => 'effect deteched'
     */
    useEffect(() => {
        console.log('effect invoked')

        return () => console.log('effect deteched')
    }, [])

    return (
        <div>
            <input value={name} onChange={e => setName(e.target.value)} />
            <button onClick={() => dispatchCount({ type: 'add' })}>{count}</button>
        </div>
    )
}

/*
 * useEffect和useLayoutEffect
 * 1. 在浏览器完成布局与绘制之后,传给 useEffect 的函数会延迟调用
 * 2. 可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。     它会在所有的 DOM 变更之后同步调用 effect。
 *  所以,组件挂载时,Layout effect invoked     =>   effect invoked
 *       组件更新时, Layouteffect deteched、Layouteffect invoked    =>      effect deteched、effect invoked
 *       组件卸载时,   effect deteched    =>   Layouteffect deteched
 */
useEffect(() => {
    console.log('effect invoked')

    return () => console.log('effect deteched')
}, [count])

useLayoutEffect(() => {
    console.log('Layout effect invoked')

    return () => console.log('Layout effect deteched')
}, [count])

3) context Hook

  • Context设计的目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题(黑夜模式)或首选语言(简体中文、英语...)。
  1. /lib下新建my-context.js
import React from 'react'

export default React.createContext('')
//=> 创建一个Context对象,初始值为''
//=> 当React渲染一个订阅了这个Context对象的组件,这个组件会从组件树中离自身最近的那个匹配的Provider中读取到当前的context值
  1. _app文件下
render() {
    const { Component, pageProps } = this.props

    return (
        <Container>
            <Layout>
                <MyContext.Provider value={this.state.context}>
                    <Component {...pageProps} />
                    <button onClick={() => this.setState({ context: `${this.state.context}111`})}>update context</button>
                </MyContext.Provider>
            </Layout>
        </Container>
    )
}
  1. 组件b
const context = useContext(MyContext)

return (
    <div>
        <input value={name} onChange={e => setName(e.target.value)} />
        <button onClick={() => dispatchCount({ type: 'add' })}>{count}</button>
        <p>{context}</p>
    </div>
)

4) Ref Hook

  • 类式组件中ref的使用,获取DOM元素的节点
class MyCount extends Component {
    constructor() {
        super();
        // 创建一个 ref 来存储 spanRef 的 DOM 元素
        this.spanRef = React.createRef()
    }

    state = {
        count: 0,
    }

    componentDidMount() {
        // React 会在组件挂载时给 current 属性传入 DOM 元素,并在组件卸载时传入 null 值
        this.refs.current
        this.interval = setInterval(() => {
            this.setState({ count: this.state.count + 1 })
        }, 1000)
    }

    componentWillUnmount() {
        if (this.interval) {
            clearInterval(this.interval)
        }
    }

    render() {
        // 告诉 React 我们想把 <span> ref 关联到构造器里创建的 `spanRef` 上
        return <span ref={this.spanRef}>{this.state.count}</span>
    }
}
  • 无状态组件引入ref,有了useRef就可以存储ref数据了
function MyCountFunc() {
    // ....
    const inputRef = useRef()

    useEffect(() => {
        // ...
        console.log(inputRef)

        // return
    }, [])

    return (
        <div>
            <input ref={inputRef} value={name} onChange={e => setName(e.target.value)} />
            {/* ... */}
        </div>
    )
}

5) Hooks渲染优化

function MyCountFunc() {
    const [count, dispatchCount] = useReducer(countReducer, 0)
    const [name, setName] = useState('firm')

    const config = {
        text: `conut is ${count}`,
        color: count > 3 ? 'red' : 'blue',
    }

    return (
        <div>
            <input value={name} onChange={e => setName(e.target.value)} />
            <Child
                config={config}
                onButtonClick={() => dispatch({ type: 'add' })}
            />
        </div>
    )
}

function Child({ onButtonClick, config }) {
    console.log('child render');
    return (
        <button onClick={onButtonClick} style={{ color: config.color }}>
            {config.text}
        </button>
    )
}

  • 问题:当前组件,无论是状态中的count还是name发生变化,都会打印出‘child render‘ ,为什么会这样?

    Child是一个无状态组件,是否重新渲染是看传给它的props(即onButtonClick和config)是否变化

  • 解决:使用memo对Child组件进行优化一下

  • React.memo

    React.memo是一个高阶组件。
    如果你的组件给定了一样的props得到同样的渲染结果,可以调用React.memo()将其包起来通过依赖项提升某些方面的性能。这就意味着React将跳过重新渲染过程,复用上次的渲染结果。
    React.memo只影响props的改变。如果你包裹在memo中的组件有用到useState或者useContext,那么当state或者context变化是,组件仍将重新渲染。
    默认情况下,它只会对props对象中的复杂对象进行浅层比较。若想要控制整个比较过程,可以自定义比较函数作为第二个参数传入。

    • 注: memo的功能和类式组件中的shouldComponentUpdate()函数很像,但是比较函数在props相等时返会true,不等时返回false。

const Child = memo(function Child({ onButtonClick, config }) {
    console.log('child render');
    return (
        <button onClick={onButtonClick} style={{ color: config.color }}>
            {config.text}
        </button>
    )
})

再次测试,结果仍然是,无论count或者name变化时都输出‘child props‘,why?

  1. count变化时,props中的config变化,Child组件中的color属性和text都变化,必然导致重新渲染,可为什么name变化,也会重新渲染Child?
  2. name是MyCountFunc组件的state,输入框中的name变化 => 触发onChange事件,setName函数执行 => name值改变,MyCountFunc组件会重新渲染 => MyCountFunc函数重新执行 => 形成一个新的函数闭包 => 形成与之对应的新的config对象(新的堆内存) => Child子组件的props发生变化 => Child组件重新渲染

所以,props还是变了。但要想不重新渲染,onButtonClick和config不能改变。如何实现呢?


  1. useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])

返回一个备忘值。
传入一个"创建"备忘值的函数,useMemo只会在依赖项变化时重新计算备忘值。这个优化能够避免渲染时的高昂开销。如果不提供依赖项,每次渲染都会得到一个新值。

在渲染是,传给useMemo的函数会执行,所以不要再渲染时做一些一般不做的事情,避免带来side effect,那是useEffect做的事情,要区分开。

useMemo是用来做性能优化的,不是作为semantic guarantee.在将来,React可能会忘记一些之前备忘的值并在下一次渲染时重新计算它们,例如为屏幕外的组件释放内存。写出的代码应该在没有useMemo时也能工作,然后用useMemo优化性能。

注意

依赖项数组并不是作为参数传给了数组。不过从概念上说,它们表示的是这意思:函数中引用的值也应出现在依赖项数组中。以后,编译器会足够高级,自动创建依赖项数组。

我们推荐在eslint-plugin-react-hooks包下使用exhaustive-deps规则。它会在不正确的指定依赖项时发出警告并建议修复。

const config = useMemo(() => ({
    text: `count is ${count}`,
    color: count > 3 ? 'red' : 'blue',
}), [count])

此时,再检测,点击按钮时,count变化 => ‘child props‘;但是,输入框中改变name时,还会输出‘child props‘,说明MyCountFunc重新渲染了,why?

onButtonClick传入的是箭头函数,而箭头函数的this是由它所定义的词法作用域决定,所以MyCountFunc重新渲染时,会生成一个新的MyCountFunc执行上下文,不同于之前的MyCountFunc,其中包含的箭头函数由于词法作用域不同,当然不同于之前的箭头函数,所以props的OnButtonClick还是改变了,必然会重新渲染。
下一步就是要优化箭头函数,使之


  1. useCallback
const memorizedCallback = useCallback(
    () => {
        doSomething(a, b)
    },
[a, b])

返回一个备忘的回调函数。

传入内联回调和依赖项数组。useCallback返回一个只在依赖项改变时才改变的备忘版回调。这在将回调传递给依靠引用相等(两个变量引用完全相等的对象)来避免不必要渲染(例如shouldComponentUpdate)的优化子组件时非常有用。

useCallback(fn, deps)相当于useMemo(() => fn, deps)

Note
依赖项数组并不是作为参数传给了数组。不过从概念上说,它们表示的是这意思:函数中引用的值也应出现在依赖项数组中。以后,编译器会足够高级,自动创建依赖项数组。
我们推荐在eslint-plugin-react-hooks包下使用exhaustive-deps规则。它会在不正确的指定依赖项时发出警告并建议修复。


function MyCountFunc() {
    const [count, dispatchCount] = useReducer(countReducer, 0)
    const [name, setName] = useState('firm')

    const config = useMemo(() => ({
        text: `count is ${count}`,
        color: count > 3 ? 'red' : 'blue',
    }), [count])

    const buttonClick = useCallback(() => {
        dispatchCount({ type: 'add' })
    }, [])

    return (
        <div>
            <input value={name} onChange={e => setName(e.target.value)} />
            <Child
                config={config}
                onButtonClick={buttonClick}
            />
        </div>
    )
}

优化后的结果

原文地址:https://www.cnblogs.com/wydumn/p/12209068.html

时间: 2024-10-09 08:32:07

React Hooks的相关文章

理解 React Hooks

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由志航发表于云+社区专栏 TL;DR 一句话总结 React Hooks 就是在 react 函数组件中,也可以使用类组件(classes components)的 state 和 组件生命周期,而不需要在 mixin. 函数组件.HOC组件和 render props 之间来回切换,使得函数组件的功能更加实在,更加方便我们在业务中实现业务逻辑代码的分离和组件的复用. 本文将从以下几个方面介绍 hooks Hooks 在解决什么问题

30分钟精通React今年最劲爆的新特性——React Hooks

你还在为该使用无状态组件(Function)还是有状态组件(Class)而烦恼吗? --拥有了hooks,你再也不需要写Class了,你的所有组件都将是Function. 你还在为搞不清使用哪个生命周期钩子函数而日夜难眠吗? --拥有了Hooks,生命周期钩子函数可以先丢一边了. 你在还在为组件中的this指向而晕头转向吗? --既然Class都丢掉了,哪里还有this?你的人生第一次不再需要面对this. 这样看来,说React Hooks是今年最劲爆的新特性真的毫不夸张.如果你也对react

react新特性 react hooks

本文介绍的是react新特性react hooks,本文面向的是有一定react开发经验的小伙伴,如果你对react还不是很熟悉的话我建议你先学习react并多多联系. 首先我们都知道react有3种组件:分别是Function函数式无状态组件.class有状态组件.高阶组件.这里不对这3种组件做一一介绍. 本文重点是react hooks 一个最简单的Hooks 首先让我们看一下一个简单的有状态组件: 1 class Example extends React.Component { 2 co

通过 React Hooks 声明式地使用 setInterval

本文由云+社区发表 作者:Dan Abramov 接触 React Hooks 一定时间的你,也许会碰到一个神奇的问题: setInterval 用起来没你想的简单. Ryan Florence 在他的推文里面说到: 不少朋友跟我提起,setInterval 和 hooks 一起用的时候,有种蛋蛋的忧伤. 老实说,这些朋友也不是胡扯.刚开始接触 Hooks 的时候,确实还挺让人疑惑的. 但我认为谈不上 Hooks 的毛病,而是 React 编程模型和 setInterval 之间的一种模式差异.

初探React Hooks &amp; SSR改造

Hooks React v16.8 发布了 Hooks,其主要是解决跨组件.组件复用的状态管理问题. 在 class 中组件的状态封装在对象中,然后通过单向数据流来组织组件间的状态交互.这种模式下,跨组件的状态管理变得非常困难,复用的组件也会因为要兼容不同的组件变得产生很多副作用,如果对组件再次拆分,也会造成冗余代码增多,和组件过多带来的问题. 后来有了 Redux 之类的状态管理库,来统一管理组件状态.但是这种分层依然会让代码变得很复杂,需要更多的嵌套.状态和方法,写代码时也常在几个文件之间不

React Hooks简单业务场景实战(非源码解读)

前言 React Hooks 是React 16.7.0-alpha 版本推出的新特性.从 16.8.0 开始,React更稳定的支持了这一新特性. 它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性. 注意:React 16.8.0 是第一个支持 Hook 的版本.升级时,请注意更新所有的 package,包括 React DOM.React Native 将在下一个稳定版本中支持 Hook. 如果说promise是JavaScript异步的终极解决方案,那

react hooks学习

接触React项目快两个月了,还在研究摸索各种知识点的过程中,充实且幸福. 在项目中学习新知识,还是很有效率的,一边写项目,一边实验新的知识点,比如react hooks!嘻嘻嘻~~~ 写了好一段时间class组件了,想尝试尝试函数式组件,之前也有试过,但是一碰到需要使用state的地方,只能又把function改成了class,心塞塞,然后没事刷博客,看到了react hooks,有一种缺什么,就有什么新知识冒出来的感觉. 1.State Hook,使用state import { useSt

React Hooks究竟是什么呢?

摘要: React Hooks原理解析. 原文:快速了解 React Hooks 原理 译者:前端小智 我们大部分 React 类组件可以保存状态,而函数组件不能? 并且类组件具有生命周期,而函数组件却不能? React 早期版本,类组件可以通过继承PureComponent来优化一些不必要的渲染,相对于函数组件,React 官网没有提供对应的方法来缓存函数组件以减少一些不必要的渲染,直接 16.6 出来的 React.memo函数. React 16.8 新出来的Hook可以让React 函数

[React + GraphQL] Use useLazyQuery to manually execute a query with Apollo React Hooks

When using useQuery from Apollo React Hooks, the request will be sent automatically after the component has been mounted. This might not be the desired behaviour as we might want to send the request in response to user action (for instance, after a b

React hooks能替代Redux,HOC和render props么?

最近开始学习React,记录一下心得. React hooks是16.8.0推出的,其目的是为了替换class,HOC,render props.同时网传hooks也将终结redux,那么本文将讨论hooks究竟能不能替换掉redux,HOC,render props. 1. Hooks替代Redux. 与其说Hooks替代Redux,不如说是Context在替代Redux.Context是16.3.0推出的(之前也有,但是API不一样且基本没人用).其实从Context开始,可以说不用redu