We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
相信大部分人都已经在使用 React hooks 了,但是在开发过程中,我们要 知其然知其所以然。整理了一下最近使用 React hooks 遇到的一些问题,如果有问题或更好的答案,欢迎一起交流。
没有hooks之前使用 render props和 高阶组件。render props是接受一个组件作为props, HOC是一个函数,接受一个组件作为参数,返回另一个组件。使用这些开发的组件会形成“嵌套地狱”,调试困难。
render props
高阶组件
props
HOC
很多组件在最开始写的时候都是很简单的,基本上就是只做一件事,当你的业务逻辑变得复杂之后,组件也会变得复杂起来。大多数情况下,我们不大可能把组件拆分的更小,因为可能有很多共用的状态逻辑,拆分后,组件之间的通信也会很复杂,甚至需要引用 Redux 来管理组件之间的状态。
要想用好 class 组件,你必须了解 ES6 中的class,理解 JavaSript 中 this的工作方式,要注意绑定事件处理器,清楚当前this的指向。
this
详细查看react 官方文档 Hook 简介
我们在平常开发中经常会遇到很多的页面有一些公共的逻辑,我们不能每次遇到的时候都直接把原来的代码 copy 过来改扒改扒,改的时候又要全局搜索改掉(很难保证没有漏的,费时费力)所以要想办法去复用,mixin、HOC , render props等都是实现逻辑复用的方式。
mixin
vue和react中都曾用过mixin(react目前已经抛弃) mixin(混入)本质上就是将对象复制到另一个对象上。
const mixin = function (obj, mixins) { const newObj = obj; newObj.prototype = Object.create(obj.prototype); for(let prop in mixins) { if(mixins.hasOwnProperty(prop)) { newObj.prototype[prop] = mixins[prop]; } } return newObj; } const obj = { sayHello() { console.log('hello'); } }; const otherObj = function() { console.log('otherObj'); } const Obj = mixin(otherObj, obj); const a = new Obj(); // otherObj a.sayHello(); // hello
mixin存在的几个问题:
HOC是React社区提出的新的方式用来取代mixin的。 高阶函数是函数式编程中一个基本的概念,它描述了一种这样的函数:接受函数作为输入,或是返回一个函数,比如 map, reduce等都是高阶函数。 高阶组件( higher-order component),类似于高阶组件接受一个组件作为参数,返回另一个组件。
function getComponent(WrappedComponent) { return class extends React.Component { render() { return <WrappedComponent {...this.props}/>; } }; }
HOC的优点为:
HOC的问题是:
render props: 通过props接受一个返回react element 的函数,来动态决定自己要渲染的结果
<DataProvider render={data => ( <h1>Hello {data.target}</h1> )}/>
React Router中就用到了 Render Props
<Router> <Route path="/home" render={() => <div>Home</div>} /> </Router>,
它有哪些问题呢
具体实现就是通过一个函数来封装跟状态有关的逻辑,将这些逻辑从组件中抽取出来。而这个函数中我们可以使用其他的Hooks,也可以单独进行测试,甚至将它贡献给社区。
import { useState, useEffect } from 'react'; function useCount() { const [count, setCount] = useState(0); useEffect(() = { document.title = `You clicked ${count} times`; }); return count }
hooks的引入就是为了解决上面提到的这么问题,因为 使用函数式组件,我们在开发组件的时候,可以当做平常写函数一样自由。
函数复用是比较容易的,直接传不同的参数就可以渲染不同的组件,复杂组件实现,我们完全可以多封装几个函数,每个函数只单纯的负责一件事。而且很多公用的代码逻辑和一些场景我们可以抽出来,封装成自定义hooks使用,比如 Umi Hooks库封装了很多共用的逻辑,比如 useSearch,封装了异步搜索场景的逻辑;比如 useVirtualList,就封装了虚拟列表的逻辑。
在使用hooks的时候,你可能会对它的规则有很多疑问,比如:
我们先来看一下官方文档给出的解释
每个组件内部都有一个「记忆单元格」列表。它们只不过是我们用来存储一些数据的 JavaScript 对象。当你用 useState() 调用一个 Hook 的时候,它会读取当前的单元格(或在首次渲染时将其初始化),然后把指针移动到下一个。这就是多个 useState() 调用会得到各自独立的本地 state 的原因。
React中是通过类似单链表的形式来实现的,通过 next 按顺序串联所有的 hook。可以看下 源码部分
export type Hook = {| memoizedState: any, baseState: any, baseQueue: Update<any, any> | null, queue: UpdateQueue<any, any> | null, next: Hook | null, |}; export type Effect = {| tag: HookEffectTag, create: () => (() => void) | void, destroy: (() => void) | void, deps: Array<mixed> | null, next: Effect, |};
更详细的推荐查看 React Hooks 原理 和 Under the hood of React’s hooks system。
我们先来看一下使用 setState 的更新机制:
在React的setState函数实现中,会根据一个变量isBatchingUpdates 判断是直接更新this.state还是放到 队列中回头再说。而isBatchingUpdates 默认是false,也就表示setState会同步更新this.state。但是,有一个函数 batchedUpdates, 这个函数会把isBatchingUpdates修改为true,而当React在调用事件处理函数之前就会调用这个batchedUpdates,造成的后果,就是由React控制的事件处理程序过程setState不会同步更新this.state。
React
setState
isBatchingUpdates
this.state
false
batchedUpdates
true
知道这些,我们下面来看两个例子。
下面的代码输出什么?
class Example extends React.Component { constructor() { super(); this.state = { val: 0 }; } componentDidMount() { this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 1 次 log this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 2 次 log setTimeout(() => { this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 3 次 log 1 this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 4 次 log 2 }, 0); } render() { return null; } };
打印结果是: 0, 0, 2, 3。
dirtyComponents
this.state.val
state.val
isBatchingUpdates为false
上面代码改用react hooks的话
import React, { useEffect, useState } from 'react'; const MyComponent = () => { const [val, setVal] = useState(0); useEffect(() => { setVal(val+1); console.log(val); setVal(val+1); console.log(val); setTimeout(() => { setVal(val+1); console.log(val); setVal(val+1); console.log(val); }, 0) }, []); return null }; export default MyComponent;
打印输出: 0, 0, 0, 0。
更新的方式没有改变。首先是因为 useEffect 函数只运行一次,其次setTimeout是个闭包,内部获取到值val一直都是 初始化声明的那个值,所以访问到的值一直是0。以例子来看的话,并没有执行更新的操作。
useEffect
setTimeout
在这种情况下,需要使用一个容器,你可以将更新后的状态值写入其中,并在以后的 setTimeout中访问它,这是useRef的一种用例。可以将状态值与ref的current属性同步,并在setTimeout中读取当前值。
useRef
ref
current
关于这部分详细内容可以查看 React useEffect的陷阱
The text was updated successfully, but these errors were encountered:
No branches or pull requests
前言
相信大部分人都已经在使用 React hooks 了,但是在开发过程中,我们要 知其然知其所以然。整理了一下最近使用 React hooks 遇到的一些问题,如果有问题或更好的答案,欢迎一起交流。
目录
一、为什么要在React中引入Hook? Hooks解决了什么问题
1. 组件复用逻辑难
没有hooks之前使用
render props
和高阶组件
。render props
是接受一个组件作为props
,HOC
是一个函数,接受一个组件作为参数,返回另一个组件。使用这些开发的组件会形成“嵌套地狱”,调试困难。2. 复杂组件状态逻辑多
很多组件在最开始写的时候都是很简单的,基本上就是只做一件事,当你的业务逻辑变得复杂之后,组件也会变得复杂起来。大多数情况下,我们不大可能把组件拆分的更小,因为可能有很多共用的状态逻辑,拆分后,组件之间的通信也会很复杂,甚至需要引用 Redux 来管理组件之间的状态。
3. class学习成本高
要想用好 class 组件,你必须了解 ES6 中的class,理解 JavaSript 中
this
的工作方式,要注意绑定事件处理器,清楚当前this的指向。二、mixin、HOC 、render props、hooks
我们在平常开发中经常会遇到很多的页面有一些公共的逻辑,我们不能每次遇到的时候都直接把原来的代码 copy 过来改扒改扒,改的时候又要全局搜索改掉(很难保证没有漏的,费时费力)所以要想办法去复用,
mixin
、HOC
,render props
等都是实现逻辑复用的方式。mixin
vue和react中都曾用过mixin(react目前已经抛弃)
mixin(混入)本质上就是将对象复制到另一个对象上。
mixin存在的几个问题:
HOC
HOC是React社区提出的新的方式用来取代mixin的。
高阶函数是函数式编程中一个基本的概念,它描述了一种这样的函数:接受函数作为输入,或是返回一个函数,比如 map, reduce等都是高阶函数。
高阶组件( higher-order component),类似于高阶组件接受一个组件作为参数,返回另一个组件。
HOC的优点为:
HOC的问题是:
Render Props
render props: 通过props接受一个返回react element 的函数,来动态决定自己要渲染的结果
React Router中就用到了 Render Props
它有哪些问题呢
使用 hooks
具体实现就是通过一个函数来封装跟状态有关的逻辑,将这些逻辑从组件中抽取出来。而这个函数中我们可以使用其他的Hooks,也可以单独进行测试,甚至将它贡献给社区。
hooks的引入就是为了解决上面提到的这么问题,因为 使用函数式组件,我们在开发组件的时候,可以当做平常写函数一样自由。
函数复用是比较容易的,直接传不同的参数就可以渲染不同的组件,复杂组件实现,我们完全可以多封装几个函数,每个函数只单纯的负责一件事。而且很多公用的代码逻辑和一些场景我们可以抽出来,封装成自定义hooks使用,比如 Umi Hooks库封装了很多共用的逻辑,比如 useSearch,封装了异步搜索场景的逻辑;比如 useVirtualList,就封装了虚拟列表的逻辑。
三、React Hooks原理
在使用hooks的时候,你可能会对它的规则有很多疑问,比如:
...
我们先来看一下官方文档给出的解释
React中是通过类似单链表的形式来实现的,通过 next 按顺序串联所有的 hook。可以看下 源码部分
四、Hooks中闭包的坑
我们先来看一下使用 setState 的更新机制:
在
React
的setState
函数实现中,会根据一个变量isBatchingUpdates
判断是直接更新this.state
还是放到 队列中回头再说。而isBatchingUpdates
默认是false
,也就表示setState
会同步更新this.state
。但是,有一个函数batchedUpdates
, 这个函数会把isBatchingUpdates
修改为true
,而当React
在调用事件处理函数之前就会调用这个batchedUpdates
,造成的后果,就是由React控制的事件处理程序过程setState
不会同步更新this.state
。知道这些,我们下面来看两个例子。
下面的代码输出什么?
打印结果是: 0, 0, 2, 3。
dirtyComponents
,所以打印时获取的都是更新前的状态 0this.state.val
都是 0,所以执行时都是将0设置为1,在react内部会被合并掉,只执行一次。设置完成后state.val
值为1。isBatchingUpdates为false
,所以能够直接进行更新,所以连着输出 2, 3上面代码改用react hooks的话
打印输出: 0, 0, 0, 0。
更新的方式没有改变。首先是因为
useEffect
函数只运行一次,其次setTimeout
是个闭包,内部获取到值val一直都是 初始化声明的那个值,所以访问到的值一直是0。以例子来看的话,并没有执行更新的操作。在这种情况下,需要使用一个容器,你可以将更新后的状态值写入其中,并在以后的
setTimeout
中访问它,这是useRef
的一种用例。可以将状态值与ref
的current
属性同步,并在setTimeout
中读取当前值。参考
The text was updated successfully, but these errors were encountered: