相信大部分人都已经在使用 React hooks 了,可是在开发过程当中,咱们要 知其然知其因此然。整理了一下最近使用 React hooks 遇到的一些问题,若是有问题或更好的答案,欢迎一块儿交流。html
没有hooks以前使用 render props
和 高阶组件
。render props
是接受一个组件做为props
, HOC
是一个函数,接受一个组件做为参数,返回另外一个组件。使用这些开发的组件会造成“嵌套地狱”,调试困难。前端
不少组件在最开始写的时候都是很简单的,基本上就是只作一件事,当你的业务逻辑变得复杂以后,组件也会变得复杂起来。大多数状况下,咱们不大可能把组件拆分的更小,由于可能有不少共用的状态逻辑,拆分后,组件之间的通讯也会很复杂,甚至须要引用 Redux 来管理组件之间的状态。vue
要想用好 class 组件,你必须了解 ES6 中的class,理解 JavaSript 中 this
的工做方式,要注意绑定事件处理器,清楚当前this的指向。react
详细查看react 官方文档 Hook 简介
咱们在日常开发中常常会遇到不少的页面有一些公共的逻辑,咱们不能每次遇到的时候都直接把原来的代码 copy 过来改扒改扒,改的时候又要全局搜索改掉(很难保证没有漏的,费时费力)因此要想办法去复用,mixin
、HOC
, render props
等都是实现逻辑复用的方式。git
vue和react中都曾用过mixin(react目前已经抛弃)
mixin(混入)本质上就是将对象复制到另外一个对象上。github
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存在的几个问题:web
HOC是React社区提出的新的方式用来取代mixin的。
高阶函数是函数式编程中一个基本的概念,它描述了一种这样的函数:接受函数做为输入,或是返回一个函数,好比 map, reduce等都是高阶函数。
高阶组件( higher-order component),相似于高阶组件接受一个组件做为参数,返回另外一个组件。面试
function getComponent(WrappedComponent) { return class extends React.Component { render() { return <WrappedComponent {...this.props}/>; } }; }
HOC的优势为:编程
HOC的问题是:api
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
。
知道这些,咱们下面来看两个例子。
下面的代码输出什么?
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
,因此打印时获取的都是更新前的状态 0this.state.val
都是 0,因此执行时都是将0设置为1,在react内部会被合并掉,只执行一次。设置完成后 state.val
值为1。isBatchingUpdates为false
,因此可以直接进行更新,因此连着输出 2, 3上面代码改用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。以例子来看的话,并无执行更新的操做。
在这种状况下,须要使用一个容器,你能够将更新后的状态值写入其中,并在之后的 setTimeout
中访问它,这是useRef
的一种用例。能够将状态值与ref
的current
属性同步,并在setTimeout
中读取当前值。
关于这部分详细内容能够查看 React useEffect的陷阱