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

来源:http://www.chinese-glasses.com 作者:Web前端 人气:178 发布时间:2020-05-06
摘要:import React, { useEffect, useState } from 'react';import ReactDOM from 'react-dom';function LifecycleDemo() { useEffect(() = { // 默认情况下,每次渲染后都会调用该函数 console.log('render!'); // 如果要实现 componentWi
import React, { useEffect, useState } from 'react';import ReactDOM from 'react-dom';function LifecycleDemo() { useEffect(() = { // 默认情况下,每次渲染后都会调用该函数 console.log('render!'); // 如果要实现 componentWillUnmount, // 在末尾处返回一个函数 // React 在该函数组件卸载前调用该方法 // 其命名为 cleanup 是为了表明此函数的目的, // 但其实也可以返回一个箭头函数或者给起一个别的名字。 return function cleanup () { console.log('unmounting...'); } }) return "I'm a lifecycle demo";}function App() { // 建立一个状态,为了方便 // 触发重新渲染的方法。 const [random, setRandom] = useState(Math.random()); // 建立一个状态来切换 LifecycleDemo 的显示和隐藏 const [mounted, setMounted] = useState(true); // 这个函数改变 random,并触发重新渲染 // 在控制台会看到 render 被打印 const reRender = () = setRandom(Math.random()); // 该函数将卸载并重新挂载 LifecycleDemo // 在控制台可以看到 unmounting 被打印 const toggle = () = setMounted(!mounted); return (  button onClick={reRender}Re-render/button button onClick={toggle}Show/Hide LifecycleDemo/button {mounted  LifecycleDemo/} / );}ReactDOM.render(App/, document.querySelector('#root'));

你还在为搞不清使用哪个生命周期钩子函数而日夜难眠吗?——拥有了Hooks,生命周期钩子函数可以先丢一边了。

这样只会在组件初次渲染的时候打印mounted,在组件卸载后打印:unmounting。

什么是Effect Hooks?

只在挂载的时候执行

很清除,我们在componentDidMount注册,再在componentWillUnmount清除注册。但假如这时候props.friend.id变了怎么办?我们不得不再添加一个componentDidUpdate来处理这种情况:

我们把reactjs的初值传递给第一个状态,这是有意义的,这个值永远不会改变。

回到一开始我们用的例子,我们分解来看到底state hooks做了什么:

使用useEffect,可以直接在函数组件内处理生命周期事件。 如果你熟悉 React class 的生命周期函数,你可以把useEffectHook 看做componentDidMount,componentDidUpdate和componentWillUnmount这三个函数的组合。来看看例子:

为什么要让副作用函数每次组件更新都执行一遍?

使用useEffect就像瑞士军刀。它可以用于很多事情,从设置订阅到创建和清理计时器,再到更改ref的值。

高阶组件这个概念就更好理解了,说白了就是一个函数接受一个组件作为参数,经过一系列加工后,最后返回一个新的组件。看下面的代码示例,withUser函数就是一个高阶组件,它返回了一个新的组件,这个组件具有了它提供的获取用户信息的功能。

各位可以只使用一个状态来存储输入,然后将相同的值发送到Reddit,但是Reddit组件会在每次按键时获取数据。

useContext useReducer useCallback useMemo useRef useImperativeMethods useMutationEffect useLayoutEffect

function App() { const [inputValue, setValue] = useState("reactjs"); const [subreddit, setSubreddit] = useState(inputValue); // Update the subreddit when the user presses enter const handleSubmit = e = { e.preventDefault(); setSubreddit(inputValue); }; return (  form onSubmit={handleSubmit} input value={inputValue} onChange={e = setValue(e.target.value)} / /form Reddit subreddit={subreddit} / / );}ReactDOM.render(App /, document.querySelector("#root"));

我们都知道react都核心思想就是,将一个页面拆成一堆独立的,可复用的组件,并且用自上而下的单向数据流的形式将这些组件串联起来。但假如你在大型的工作项目中用react,你会发现你的项目中实际上很多react组件冗长且难以复用。尤其是那些写成class的组件,它们本身包含了状态,所以复用这类组件就变得很麻烦。

这仍然是硬编码的,但是现在咱们可以通过包装Reddit组件来定制它,该组件允许咱们更改subreddit。

其实我们看hook的“形态”,有点类似之前被官方否定掉的Mixins这种方案,都是提供一种“插拔式的功能注入”的能力。而mixins之所以被否定,是因为Mixins机制是让多个Mixins共享一个对象的数据空间,这样就很难确保不同Mixins依赖的状态不发生冲突。

不传递第二个参数会导致每次渲染都会运行useEffect。然后,当它运行时,它获取数据并更新状态。然后,一旦状态更新,组件将重新呈现,这将再次触发useEffect,这就是问题所在。

在这样的背景下,Hooks便横空出世了!

与componentDidMount、componentDidUpdate不同的是,在浏览器完成布局与绘制之后,传给useEffect的函数会延迟调用。这使得它适用于许多常见的副作用场景,比如如设置订阅和事件处理等情况,因此不应在函数中执行阻塞浏览器更新屏幕的操作。

... componentDidUpdate { // 先把上一个friend.id解绑 ChatAPI.unsubscribeFromFriendStatus( prevProps.friend.id, this.handleStatusChange ); // 再重新注册新但friend.id ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); }...
useEffect(() = { console.log('mounted'); return () = console.log('unmounting...');}, []) 

这样看来,说React Hooks是今年最劲爆的新特性真的毫不夸张。如果你也对react感兴趣,或者正在使用react进行项目开发,答应我,请一定抽出至少30分钟的时间来阅读本文好吗?所有你需要了解的React Hooks的知识点,本文都涉及到了,相信完整读完后你一定会有所收获。

在这个例子中,一起来看下如何使用useEffect和useRefhook 将input控件聚焦在第一次渲染上。

1.页面首次渲染2.替friend.id=1的朋友注册3.突然friend.id变成了24.页面重新渲染5.清除friend.id=1的绑定6.替friend.id=2的朋友注册...

顺便说一下:输入的时候要小心,因为没有错误处理,所以当你输入的subreddit不存在,应用程序将会爆炸,实现错误处理就作为你们的练习。

 componentDidMount() { ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ); }

上面这个示例中,咱们传入[value]作为第二个参数。这个参数是什么作用呢?如果value的值是5,而且咱们的组件重渲染的时候value还是等于 5,React 将对前一次渲染的[5]和后一次渲染的[5]进行比较。因为数组中的所有元素都是相等的(5 === 5),React 会跳过这个effect,这就实现了性能的优化。

import { useState } from 'react';function Example() { const [count, setCount] = useState; return (  You clicked {count} times  setCount}> Click me   );}

import { useState } from 'react';function Example() { const [count, setCount] = useState;

为啥每次渲染都会打印 'unmounting'。咱们可以有选择性地从useEffect返回的cleanup函数只在组件卸载时调用。React 会在组件卸载的时候执行清除操作。正如之前学到的,effect在每次渲染的时候都会执行。这就是为什么 React 会在执行当前 effect 之前对上一个effect进行清除。这实际上比componentWillUnmount生命周期更强大,因为如果需要的话,它允许咱们在每次渲染之前和之后执行副作用。

这里有一个点需要重视!这种解绑的模式跟componentWillUnmount不一样。componentWillUnmount只会在组件被销毁前执行一次而已,而useEffect里的函数,每次组件渲染后都会执行一遍,包括副作用函数返回的这个清理函数也会重新执行一遍。所以我们一起来看一下下面这个问题。

首先,咱们更改Reddit组件以接受subreddit作为一个prop,并基于该subreddit获取数据,只有当prop更改时才重新运行effect.

 //第一次渲染 useState; //将age初始化为42 useState; //将fruit初始化为banana useState([{ text: 'Learn Hooks' }]); //... //第二次渲染 useState; //读取状态变量age的值 useState; //读取状态变量fruit的值 useState([{ text: 'Learn Hooks' }]); //...

let showFruit = true;function ExampleWithManyStates() { const [age, setAge] = useState { const [fruit, setFruit] = useState; showFruit = false; } const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);

 //第一次渲染 useState; //将age初始化为42 useState; //将fruit初始化为banana useState([{ text: 'Learn Hooks' }]); //... //第二次渲染 useState; //读取状态变量age的值 // useState; useState([{ text: 'Learn Hooks' }]); //读取到的却是状态变量fruit的值,导致报错

下面是一个组件,它从Reddit获取帖子并显示它们

看到了吗?很繁琐,而我们但useEffect则没这个问题,因为它在每次组件更新后都会重新执行一遍。所以代码的执行顺序是这样的:

顶部的useState看起来有点奇怪,尤其是第二行:

什么是State Hooks?

const [inputValue, setValue] = useState("reactjs");const [subreddit, setSubreddit] = useState(inputValue);

假如一个组件有多个状态值怎么办?

为了解决这个问题,我们需要传递一个数组作为第二个参数,数组内容又是啥呢。

以上这两种模式看上去都挺不错的,很多库也运用了这种模式,比如我们常用的React Router。但我们仔细看这两种模式,会发现它们会增加我们代码的层级关系。最直观的体现,打开devtool看看你的组件层级嵌套是不是很夸张吧。这时候再回过头看我们上一节给出的hooks例子,是不是简洁多了,没有多余的层级嵌套。把各种想要的功能写成一个一个可复用的自定义hook,当你的组件想用什么功能时,直接在组件里调用这个hook即可。

仅在挂载和卸载的时候执行

还是看上面给出的ExampleWithManyStates例子,我们调用了三次useState,每次我们传的参数只是一个值,我们根本没有告诉react这些值对应的key是哪个,那react是怎么保证这三个useState找到它对应的state呢?

useEffect所依赖的唯一变量是setPosts。因此,咱们应该在这里传递数组[setPosts]。因为setPosts是useState返回的setter,所以不会在每次渲染时重新创建它,因此effect只会运行一次。

答案是,react是根据useState出现的顺序来定的。我们具体来看一下:

单击“Show/Hide”按钮,看看控制台,它在消失之前打印“unmounting...”,并在它再次出现时打印 “render!”。现在,点击Re-render按钮。每次点击,它都会打render!,还会打印umounting,这似乎是奇怪的。

我们用class来创建react组件时,还有一件很麻烦的事情,就是this的指向问题。为了保证this的指向正确,我们要经常写这样的代码:this.handleClick = this.handleClick.bind,或者是这样的代码:this.handleClick}>。一旦我们不小心忘了绑定this,各种bug就随之而来,很麻烦。

// 从props中解构`subreddit`:function Reddit({ subreddit }) { const [posts, setPosts] = useState([]); useEffect(async () = { const res = await fetch( `${subreddit}.json` ); const json = await res.json(); setPosts(json.data.children.map(c = c.data)); // 当`subreddit`改变时重新运行useEffect: }, [subreddit, setPosts]); return ( ul {posts.map(post = ( li key={post.id}{post.title}/li ))} /ul );}ReactDOM.render( Reddit subreddit='reactjs' /, document.querySelector("#root"));
class Example extends React.Component { constructor; this.state = { count: 0 }; } render() { return (  You clicked {this.state.count} times  this.setState({ count: this.state.count + 1 })}> Click me   ); }}

然而,并非所有effect都可以被延迟执行。例如,在浏览器执行下一次绘制前,用户可见的 DOM 变更就必须同步执行,这样用户才不会感觉到视觉上的不一致。(概念上类似于被动监听事件和主动监听事件的区别。)React为此提供了一个额外的useLayoutEffectHook 来处理这类effect。它和useEffect的结构相同,区别只是调用时机不同。

所以我们做的事情其实就是,声明了一个状态变量count,把它的初始值设为0,同时提供了一个可以更改count的函数setCount。

当数据改变时重新获取

You clicked {count} times

虽然useEffect会在浏览器绘制后延迟执行,但会保证在任何新的渲染前执行。React 将在组件更新前刷新上一轮渲染的effect。

import Cat from 'components/cat'class DataProvider extends React.Component { constructor; this.state = { target: 'Zac' }; } render() { return (  {this.props.render }} ( )}/>虽然这个模式叫Render Props,但不是说非用一个叫render的props不可,习惯上大家更常写成下面这种:... {data => (  )}

在CodeSandbox中尝试一下。

我们再梳理一遍下面代码的逻辑:

许许多多的用途

你还在为该使用无状态组件还是有状态组件而烦恼吗?——拥有了hooks,你再也不需要写Class了,你的所有组件都将是Function。

import React, { useEffect, useState, useRef } from "react";import ReactDOM from "react-dom";function App() { // 存储对 input 的DOM节点的引用 const inputRef = useRef(); // 将输入值存储在状态中 const [value, setValue] = useState(""); useEffect( () = { // 这在第一次渲染之后运行 console.log("render"); // inputRef.current.focus(); }, // effect 依赖 inputRef [inputRef] ); return ( input ref={inputRef} value={value} onChange={e = setValue(e.target.value)} / );}ReactDOM.render(App /, document.querySelector("#root"));

那之前,官方推荐怎么解决这个问题呢?答案是:渲染属性和高阶组件(Higher-Order Components)。我们可以稍微跑下题简单看一下这两种模式。

如果想执行只运行一次的effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的effect不依赖于props或state中的任何值,所以它永远都不需要重复执行。这并不属于特殊情况 —— 它依然遵循依赖数组的工作方式。

我们写的有状态组件,通常会产生很多的副作用,比如发起ajax请求获取数据,添加一些监听的注册和取消注册,手动修改dom等等。我们之前都把这些副作用的函数写在生命周期函数钩子里,比如componentDidMount,componentDidUpdate和componentWillUnmount。而现在的useEffect就相当与这些声明周期函数钩子的集合体。它以一抵三。

useEffect在每次渲染后运行(默认情况下),并且可以选择在再次运行之前自行清理。

首先,我们声明了一个状态变量count,将它的初始值设为0。然后我们告诉react,我们的这个组件有一个副作用。我们给useEffecthook传了一个匿名函数,这个匿名函数就是我们的副作用。在这个例子里,我们的副作用是调用browser API来修改文档标题。当react要渲染我们的组件时,它会先记住我们用到的副作用。等react更新了DOM之后,它再依次执行我们定义的副作用函数。

使用 useEffect 获取数据

function ExampleWithManyStates() { const [age, setAge] = useState; const [fruit, setFruit] = useState; const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
import React, { useEffect, useState } from "react";import ReactDOM from "react-dom";function Reddit() { const [posts, setPosts] = useState([]); useEffect(async () = { const res = await fetch( "" ); const json = await res.json(); setPosts(json.data.children.map(c = c.data)); }); // 这里没有传入第二个参数,你猜猜会发生什么? // Render as usual return ( ul {posts.map(post = ( li key={post.id}{post.title}/li ))} /ul );}ReactDOM.render( Reddit /, document.querySelector("#root"));

为什么要自己去写一个Effect Hooks? 这样我们才能把可以复用的逻辑抽离出来,变成一个个可以随意插拔的“插销”,哪个组件要用来,我就插进哪个组件里,so easy!看一个完整的例子,你就明白了。

这个应用程序在这里保留了两个状态:当前的输入值和当前的subreddit。提交表单将提交subreddit,这会导致Reddit重新获取数据。

上面这种表达形式,是借用了es6的数组解构,它可以让我们的代码看起来更简洁。不清楚这种用法的可以先去看下我的这篇文章30分钟掌握ES6/ES2015核心内容。

作者:Dave Ceddia译者:前端小智原文链接:-hook-examples/

更新状态

在 CodeSandbox 试试这个示例。

function FriendListItem { const isOnline = useFriendStatus; return ( 

注意到咱们没有将第二个参数传递给useEffect,这是不好的,不要这样做。

从ExampleWithManyStates函数我们可以看到,useState无论调用多少次,相互之间是独立的。这一点至关重要。为什么这么说呢?

虚接着扩展一下示例,以涵盖另一个常见问题:如何在某些内容发生更改时重新获取数据,例如用户ID,名称等。

除了useState这个hook外,还有很多别的hook,比如useEffect提供了类似于componentDidMount等生命周期钩子的功能,useContext提供了上下文的功能等等。

时间: 2019-09-06阅读: 256标签: hooks

import { useState } from 'react';function Example() { const [count, setCount] = useState; return (  You clicked {count} times  setCount}> Click me   );}

阻止每次重新渲染都会执行 useEffect

除了上文重点介绍的useState和useEffect,react还给我们提供来很多有用的hooks:

记住,useState是有状态的。它只使用初始状态一次,即第一次渲染,之后它就被忽略了。所以传递一个瞬态值是安全的,比如一个可能改变或其他变量的prop。

而现在我们的hook,一方面它是直接用在function当中,而不是class;另一方面每一个hook都是相互独立的,不同组件调用同一个hook也能保证各自状态的独立性。这就是两者的本质区别了。

不过,这隐藏了一个问题:传递空数组容易出现bug。如果咱们添加了依赖项,那么很容易忘记向其中添加项,如果错过了一个依赖项,那么该值将在下一次运行useEffect时失效,并且可能会导致一些奇怪的问题。

如果不用数组解构的话,可以写成下面这样。实际上数组解构是一件开销很大的事情,用下面这种写法,或者改用对象解构,性能会有很大的提升。具体可以去这篇文章的分析Array destructuring for multi-value returns (in light of React hooks),这里不详细展开,我们就按照官方推荐使用数组解构就好。

不完全的生命周期

我们对比着看一下,如果没有hooks,我们会怎么写?

const [value, setValue] = useState('initial');useEffect(() = { // 仅在 value 更改时更新 console.log(value);}, [value]) 

简直Fabulous!

因此,即使咱们将[inputRef]作为useEffect的第二个参数传递,它实际上只在初始挂载时运行一次。这基本上是componentDidMount效果了。

首先,useState是可以多次调用的,所以我们完全可以这样写:

那么第二行呢,如果初始状态改变了呢,如当你输入box时候。

渲染属性指的是使用一个值为函数的prop来传递需要动态渲染的nodes或组件。如下面的代码可以看到我们的DataProvider组件包含了所有跟状态相关的代码,而Cat组件则可以是一个单纯的展示型组件,这样一来DataProvider就可以单独复用了。

再来看看另一个常见的用例:获取数据并显示它。在类组件中,无们通过可以将此代码放在componentDidMount方法中。在 hook 中可以使用 useEffect hook 来实现,当然还需要用useState来存储数据。

生命周期钩子函数里的逻辑太乱了吧!

在顶部,我们使用useRef创建一个空的ref。 将它传递给input的ref prop,在渲染DOM 时设置它。 而且,重要的是,useRef返回的值在渲染之间是稳定的 - 它不会改变。

读取状态值

与其将useEffect看作一个函数来完成3个独立生命周期的工作,不如将它简单地看作是在渲染之后执行副作用的一种方式,包括在每次渲染之前和卸载之前咱们希望执行的需要清理的东西。

class Example extends React.Component { constructor; this.state = { count: 0 }; } componentDidMount() { document.title = `You clicked ${this.state.count} times`; } componentDidUpdate() { document.title = `You clicked ${this.state.count} times`; } render() { return (  You clicked {this.state.count} times  this.setState({ count: this.state.count + 1 })}> Click me   ); }}

如果希望effect较少运行,可以提供第二个参数 - 值数组。 将它们视为该effect的依赖关系。 如果其中一个依赖项自上次更改后,effect将再次运行。

想要复用一个有状态的组件太麻烦了!

本文由10bet发布于Web前端,转载请注明出处:30分钟精通React今年最劲爆的新特性——React Hoo

关键词:

最火资讯