1. 首页
  2. Reactjs

how to use react hook【编辑中】

HOC => HOOK

Hook 与 Recompose 的关联

A Note from the Author (acdlite, Oct 25 2018): Hi! I created Recompose about three years ago. About a year after that, I joined the React team. Today, we announced a proposal for Hooks. Hooks solves all the problems I attempted to address with Recompose three years ago, and more on top of that. I will be discontinuing active maintenance of this package (excluding perhaps bugfixes or patches for compatibility with future React releases), and recommending that people use Hooks instead. Your existing code with Recompose will still work, just don’t expect any new features. Thank you so, so much to @wuct and @istarkov for their heroic work maintaining Recompose over the last few years.

上述引用是 recompose 作者 acdlite 发表的一则声明,这里做个简要翻译:recompose 还可以用,但是! 我不做后续的功能迭代了。因为我加入react 团队了,今天,我们推出了 Hook ,一个可以完美代替 recompose 的新方案。

为什么会有Hook

首先,我们来看一下近年常用的高阶组件(HOC),这里我们以 recompose 为例,以下为使用 recompose 生成的高阶组件(HOC)。

how to use react hook【编辑中】

由上图所示,通过 recompose 实现对组件 DataCollectSystemRoleManagement 的 逻辑复用,但是很明显暴露出一个弊端:我们的组件会被层层叠叠的 provider 包裹着。并且如果一开始没设计好状态逻辑,后续业务的更新迭代,我们需要重新组织组件结构,以致于代码变得愈加繁重,难以理解。这时候,就需要一个更底层的方案来解决逻辑状态复用的问题,所以 Hooks 应运而生。

Hook api 详解

useState

不使用useState实现计数器


import React, { Component } from "react"; import { Button, Badge, Icon } from "antd"; class Demo extends Component { state = { count: 1 }; handleAdd = () => { const { count } = this.state; this.setState({ count: count + 1 }); }; handleReduce = () => { const { count } = this.state; this.setState({ count: count - 1 }); }; render() { const { count } = this.state; return ( <div> <Button type="primary" onClick={this.handleAdd}> <Icon type="plus" /> </Button>{" "} <Button type="primary" onClick={this.handleReduce}> <Icon type="minus" /> </Button>{" "} <Badge count={count} /> </div> ); } }

使用useState实现计数器


import React, { useState } from "react"; import { Button, Badge, Icon } from "antd"; const handelAdd = count => count + 1; const handleReduce = count => count - 1; const Demo = () => { const [count, setCount] = useState(1); return ( <div> <Button type="primary" onClick={() => setCount(handelAdd(count))}> <Icon type="plus" /> </Button>{" "} <Button type="primary" onClick={() => setCount(handleReduce(count))} > <Icon type="minus" /> </Button>{" "} <Badge count={count} /> </div> ); };

useState做了什么

定义了一个 count 变量,赋予初始值 0 。并提供一个专门改变这个变量的方法 setCount,setCount 方法接收一个值,更新当前 count替换当前值) 。

知识点

  • 每个 useState 只能定义一个变量以及一个与之对应该变量更改方法,变量与方法一对一匹配
  • 想定义多个变量就多写几个 useState
  • useState 仅接收一个参数,即为 变量的初始值
  • useState 返回的变量可以很好的存储对象和数组,因此根据业务我们有时候没必要定义多个变量,将业务需要的状态存储以数组或者对象的方式存储到 变量中即可(注意:useState 返回的改变变量的做的是替换动作,而不是合并)

useEffect

不使用uesEfffect


import React, { Component } from "react"; import { Button, Badge, Icon } from "antd"; // 通过 console 记录所有 count 值 class Demo extends Component { state = { count: 1 }; componentDidMount() { const { count } = this.state; console.log("count===>", count); } componentDidUpdate() { const { count } = this.state; console.log("count===>", count); } handleAdd = () => { const { count } = this.state; this.setState({ count: count + 1 }); }; render() { const { count } = this.state; return ( <div style={{ margin: 50 }}> <Button type="primary" onClick={this.handleAdd}> <Icon type="plus" /> </Button>{" "} <Badge count={count} /> </div> ); } }

使用useEffect


import React, { useState, useEffect } from "react"; import { Button, Badge, Icon } from "antd"; const handleAdd = count => count + 1; // 通过 console 记录所有 count 值 const Demo = () => { const [count, setCount] = useState(1); useEffect(() => console.log("count===>", count)); return ( <div style={{ margin: 50 }}> <Button type="primary" onClick={() => setCount(handleAdd(count))}> <Icon type="plus" /> </Button>{" "} <Badge count={count} /> </div> ); };

useEffect做了什么

useEffect 可以让你在函数组件中执行副作用操作,他相当于组件的 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个周期的集合体,会在这些生命周期触发时执行副作用操作。

参数 useEffect(callback, [count])

  • 回调函数:在组件第一个 rendercomponentDidMount)和之后每次 upDatecomponentDidUpdate)的时候运行 【注意:是每次挂载、更新完毕的时候才会执行该回调函数】
  • 回调函数返回值:useEffect 的第一个参数可以返回一个函数,当页面更新后,即将执行下一 useEffect 时,进行调用,通常这个函数用来对上一次 useEffect 一些处理(清理)
import React, { useState, useEffect } from "react";

const Demo = () => {
  const [count, setCount] = useState(0);

  console.log(`页面更新中 ===> ${count}`);

  useEffect(() => {
    console.log(`页面更新完毕 ===> ${count}`);

    return () => {
      console.log(`页面更新之前(执行上次更新时的useEffect返回的函数) ===> ${count}`);
    };
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
};

// componentDidMuout
页面更新中 ===> 0
页面更新完毕 ===> 0
// componentDidUpdate(click button one time)
页面更新中 ===> 1
页面更新之前(执行上次更新时的useEffect返回的函数) ===> 0   // 此时的值是上次count的值
页面更新完毕 ===> 1
// componentDidUpdate(click button two times)
页面更新中 ===> 2
页面更新之前(执行上次更新时的useEffect返回的函数)===> 1  // 此时的值是上次count的值
页面更新完毕 ===> 2

  • 状态依赖 [count]:当配置了状态依赖项后,只有检测到配置的状态发生变化时,才会执行该回调函数。

useEffect(() => { // 只要组件更新(render)完毕就会执行 }) useEffect(() => { // 只有组件更新(render)完毕并且 count 发生改变才会执行 }, [count])

知识点

  • 为什么组件渲染完毕之后,再执行effect callback return的方法时还可以取到上次state的值?因为Hook在useEffect中返回的是一个函数,形成了一个闭包,保证了我们上一次执行函数存储的变量不会被销毁和污染。
  • 不像componentDidMount或者componentDidUpdate,useEffect中使用effect并不会阻滞浏览器渲染页面,会让网页/App看起来更加流畅

useContext

使用传统context


import React from "react"; import { Button } from "antd"; const GaryContext = React.createContext("gray"); const RedContext = React.createContext("red"); const Content = () => { return ( <GaryContext.Consumer> {gray => ( <RedContext.Consumer> {red => { return ( <Button size="large" type="primary"> {gray} {red} </Button> ); }} </RedContext.Consumer> )} </GaryContext.Consumer> ); }; const Box = () => { return <Content />; }; const Demo = () => { return ( <GaryContext.Provider value={"gary1"}> <RedContext.Provider value={"red1"}> <Box /> </RedContext.Provider> </GaryContext.Provider> ); }; export default Demo;

使用useContext


import React, { useContext } from "react"; import { Button } from "antd"; const GaryContext = React.createContext("gray"); const RedContext = React.createContext("red"); const Context = () => { const gray = useContext(GaryContext); const red = useContext(RedContext); return ( <Button size="large" type="primary"> {gray} {red} </Button> ); }; const Box = () => { return <Context />; }; const Demo = () => { return ( <GaryContext.Provider value={"gary1"}> <RedContext.Provider value={"red1"}> <Box /> </RedContext.Provider> </GaryContext.Provider> ); }; export default Demo;

useContext做了什么

useContext是针对context做的进一步封装,对比context使用起来更加简洁。解决了传统的context 当存在多个Provider时造成的多个Consumer嵌套问题。(从上述代码对比可明显看出差异)


useReducer

使用useReducer


import React, { useReducer } from "react"; import { Button, Icon, Badge } from "antd"; const initialState = { count: 0 }; const reducer = (state, action) => { const { type, payload } = action; const { count } = state; switch (type) { case "increment": return { count: count + payload }; case "decrement": return { count: count - payload }; default: throw new Error(); } }; const Demo = () => { const [state, dispatch] = useReducer(reducer, initialState); const { count } = state; return ( <div style={{ margin: 50 }}> <Button type="primary" onClick={() => dispatch({ type: "increment", payload: 5 })} > <Icon type="plus" /> </Button>{" "} <Button type="primary" onClick={() => dispatch({ type: "decrement", payload: 5 })} > <Icon type="minus" /> </Button>{" "} <Badge count={count} /> </div> ); }; export default Demo;

useReducer做了什么

对redux的仿写,算是对uesState的进一步封装,针对一些复杂的业务场景。熟悉redux的一目了然,这里不做赘述。

知识点

如果初始值需要一些逻辑处理,则可以useReducer接收第三个参数,第三个参数为一个函数,该函数将useReducer的第二个参数作为参数,返回一个值作为初始值,具体示例代码如下


const init = initialCount => { // 初始值逻辑操作 return {count: initialCount}; } ... const Demo = initialCount => { const [state, dispatch] = useReducer(reducer, initialCount, init); ... }

useCallback

在使用 useCallback 之前,我们要了解一下 react 的更新机制


class Demo1 extends Component { render() { return ( <div> <SomeComponent style={{ fontSize: 14 }} doSomething={() => { console.log("do something"); }} /> </div> ); } }

如上代码,当Demo1组件重新render, SomeComponent 组件的属性赋值语句会再次执行一遍,从而导致生成新的属性(即使赋值语句内容没有发生改变),促使SomeComponent组件发生更新,造成不必要的渲染。 通常我们通过以下方式解决上述问题。


const fontSizeStyle = { fontSize: 14 }; class Demo2 extends Component { doSomething = () => { console.log("do something"); }; render() { return ( <div> <SomeComponent style={fontSizeStyle} doSomething={this.doSomething} /> </div> ); } }

通过将赋值语句抽离,存储在组件外部,或者类组组件this中,避免产生不必要渲染。

为什么要使用useCallback

在函数式组件中,因为不存在this,所以无法用this存储,在函数式组件内定义的函数依旧存在组件更新,函数自动执行,从而改变子组件属性的情况,useCallback解决了这个问题。


const Demo = () => { const handleClick = useCallback(() => { console.log("Click happened"); }, []); // 空数组代表无论什么情况下该函数都不会发生改变 return ( <div> <SomeComponent onClick={handleClick}>Click Me</SomeComponent>; </div> ); };

老规矩,useCallback第二个参数传入一个数组,数组中的每一项一旦值或者引用发生改变,useCallback 就会重新返回一个新的记忆函数提供给后面进行渲染。这样只要子组件做了渲染优化(PureComponent, componentShouldUpdate) 就可以有效避免不必要的 VDOM 渲染。


useMemo【attention】

功能与 useCallback 一致。区别:

  • useCallback()直接将第一个参数函数返回,常用于记忆时间函数
  • useMemo()会执行第一个参数函数,然后返回该参数函数的返回结果,常用于 需要通过逻辑计算结果 如记忆组件

const Demo = () => { const handleClick = useMemo( () => () => { console.log("Click happened"); }, [] // 空数组代表无论什么情况下该函数都不会发生改变 ); return <SomeComponent onClick={handleClick}>Click Me</SomeComponent>; };

useRef


const Demo = () => { const nameRef = useRef(); return <input ref={nameRef} type="text" />; };

知识点

  • 通过useRef() 拿到的ref对象,通过.current 就可以拿到对应的DOM对象。

useImperativeHandle

useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用:


import React, { useRef, useEffect, useImperativeHandle, forwardRef } from "react"; const ChildInputComponent = (props, ref) => { const inputRef = useRef(null); useImperativeHandle(ref, () => inputRef.current); return <input type="text" name="child input" ref={inputRef} />; }; const ChildInput = forwardRef(ChildInputComponent); const Demo = () => { const inputRef = useRef(null); // 这时候父组件能够拿到子组件的 ref 对象 console.log('inputRef===>', inputRef); return ( <div> <ChildInput ref={inputRef} /> </div> ); };

useLayoutEffect

大部分情况下,使用 useEffect 就可以帮我们处理组件的副作用,但是如果想要同步调用一些副作用,比如对 DOM 的操作,就需要使用 useLayoutEffect,useLayoutEffect 中的副作用会在 DOM 更新之后同步执行。


const Demo = () => { useLayoutEffect(() => { console.log("useLayoutEffect"); // 优先输出 }); useEffect(() => { console.log("useEffect"); }); return ( <div> <h1>23333</h1> </div> ); };

由于使用useLayoutEffect会堵塞视图的更新,所以在能使用uesEffect的情况下,优先使用useEffect。


扩展

如何获取上一个props

在日常开发中,我们有时候会需要获取当前props以及上一个props做一些逻辑操作,通常我们是通过componentWillReciveProps获取的,但是在hook中并没有直接暴露,所以我们需要通过一个自定义hookl来实现这个需求。


const Demo = () => { const [count, setCount] = useState(0); const prevCount = usePrevious(count); return ( <h1> Now: {count}, before: {prevCount} </h1> ); } const usePrevious = value => { const ref = useRef(); useEffect(() => { ref.current = value; }); return ref.current; }

使用useMemo实现子组件的渲染控制


const Demo = ({ a, b }) => { // Only re-rendered if `a` changes: const child1 = useMemo(() => <Child1 a={a} />, [a]); // Only re-rendered if `b` changes: const child2 = useMemo(() => <Child2 b={b} />, [b]); return ( <div> {child1} {child2} <div/> ); };

我们可以使用useMemo生成一个memo子组件,这样子组件内部就不必做一些是否渲染判断(PureComponent,showComponentDidUpedate)

作者:蒜了吧
链接:https://juejin.im/post/5d01c3d2518825259c0336e5

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「IT平头哥联盟」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文著作权归作者所有,如若转载,请注明出处

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:how to use react hook【编辑中】

链接:https://www.javascriptc.com/3787.html

« react hook用法及实现原理
让你可以在2020年成为前端大师的9个项目»
Flutter 中文教程资源

相关推荐

QR code