You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
当然这个错不在 React,而在于 JavaScript 的 this 指向问题,简单看这样的代码:
classAnimal{constructor(name){this.name=name;}getName(){console.log(this);console.log(this.name)}}constT=newAnimal('cat');T.getName();// `this` is Animal Instance called CatvarP=T.getName;P();// `this` is undefined
这个例子和上面的 React 如出一辙,在没有 bind 的情况下这样写会 导致了 this 是 global this,即 undefined。
classAnimal{constructor(name){this.name=name;}getName=()=>{console.log(this);console.log(this.name)}}constT=newAnimal('cat');T.getName();// `this` is Animal Instance called CatvarP=T.getName;P();// `this` is Animal Instance called Cat
箭头函数不会创建自己的 this,只会依照词法从自己的作用域链的上一层继承 this,从而会让这里的 this 指向恰好和我们要的一致。
即使 public class fileds syntax 借助 arrow function 可以勉强解决这种问题,但 this 指向的问题依旧让人 “恐慌”。
Webhooks allow you to build or set up GitHub Apps which subscribe to certain events on GitHub.com. When one of those events is triggered, we'll send a HTTP POST payload to the webhook's configured URL. Webhooks can be used to update an external issue tracker, trigger CI builds, update a backup mirror, or even deploy to your production server. You're only limited by your imagination.
—— GitHub WebHook 介绍
核心是:When one of those events is triggered, we'll send a HTTP POST payload to the webhook's configured URL
2.2 什么是 React Hooks
那 React Hooks 又是什么呢?
Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.
看上去和 WebHook 区别不小,实际上也不小,怎么解释呢?
React Hook 在 Function Component 上开了一些 Hook,通过内置的一些 Hook 可以让 Function Component 拥有自己的 state 和 生命周期,同时可以在 Hook 中操作它。此外通过组合内置的 Hook + 自己的业务逻辑 就可以生成新的 Custom Hook,以方便优雅复用一些业务逻辑。
从人的角度上讲,class component 需要关心 this 指向等,大多经常在使用 function component 还是 class component 上感到困惑;从机器的角度上讲,class component 编译体积过大,热重载不稳定
四、Hooks 有哪些功能
上述提到的三个问题,Hooks 某种意义上都做了自己的解答,那是如何解答的呢?
目前 Hooks 有: State Hook、Effect Hook、Context Hook、以及 Custom Hook。
4.1 State Hook
importReact,{useState}from'react';functionExample(){// Declare a new state variable, which we'll call "count"const[count,setCount]=useState(0);return(<div><p>You clicked {count} times</p><buttononClick={()=>setCount(count+1)}>
Click me
</button></div>);}
useState 是 State Hook 的 API。入参是 initialState,返回一个 数组,第一值是 state,第二个值是改变 state 的函数。
同时为了避免不必要的性能开销,在设置 State 的时候如果两个值是相等的,则也不会触发 rerender。(判断两个值相等使用的是 Object.is)
4.2 Effect Hook
importReact,{useState,useEffect}from'react';functionExample(){const[count,setCount]=useState(0);// Similar to componentDidMount and componentDidUpdate:useEffect(()=>{// Update the document title using the browser APIdocument.title=`You clicked ${count} times`;return()=>{
... // Similar to componentWillUnMount。Named as clear up effect}});return(<div><p>You clicked {count} times</p><buttononClick={()=>setCount(count+1)}>
Click me
</button></div>);}
一、前言
1.1 为何要优先使用 SFC(Stateless Function Component)
Stateless Function Component:
Class Component:
上面是两个最简单的 function component 和 class component 的对比,首先从行数上来看,3 << 7。
再看 babel 编译成 es2015 后的代码:
Function Component:
Class Component:
Function Component 仅仅是一个普通的 JS 函数,Class Component 因为 ES2015 不支持
class
的原因,会编译出很多和 class 相关的代码。同时因为 Function Component 的特殊性,React 底层或许可以做 更多的性能优化。
总的来说,以下点:
1.2 恼人的
bind(this)
在 React Class Component 中,我们一定写过很多这样的代码
当然这个错不在 React,而在于 JavaScript 的 this 指向问题,简单看这样的代码:
这个例子和上面的 React 如出一辙,在没有 bind 的情况下这样写会 导致了 this 是 global this,即 undefined。
解决它比较好的办法就是在 contructor 里面 bind this。
在新版本的 ES 中,有 Public Class Fields Syntax 可以解决这个问题,即:
箭头函数不会创建自己的 this,只会依照词法从自己的作用域链的上一层继承 this,从而会让这里的 this 指向恰好和我们要的一致。
即使 public class fileds syntax 借助 arrow function 可以勉强解决这种问题,但 this 指向的问题依旧让人 “恐慌”。
1.2 被废弃的几个生命周围
React 有非常多的生命周期,在 React 的版本更新中,有新的生命周期进来,也有一些生命周期官方已经渐渐开始认为是
UNSAFE
。目前被标识为UNSAFE
的有:新引入了
getDerivedStateFromProps
和getSnapshotBeforeUpdate
均是返回一个处理后的对象给componentDidUpdate
,所有需要操作的逻辑都放在componentDidUpdate
里面。原则上:
getDerivedStateFromProps
+componentDidUpdate
可以替代componentWillReceiveProps
的所有正常功能;getSnapshotBeforeUpdate
+componentDidUpdate
可以替代componentWillUpdate
的所有功能。具体的 原因 和 迁移指南 可以参考 React 的官方博客:Update on Async Rendering,有比较详实的手把手指南。
最后,你应该依旧是同样的感觉,Class Component 有如此多的生命周期,显得是如此的复杂。
说了上面一堆看似和题目无关的话题,其实就是为了让你觉得 “Function Component 大法好”,然后再开心的看下文。
二、什么是 React Hooks
终于来到了和 Hooks 相关的部分,首先我们看下 什么是 Hooks:
2.1 什么是 Hooks
首先来看下我们熟知的 WebHook:
核心是:When one of those events is triggered, we'll send a HTTP POST payload to the webhook's configured URL
2.2 什么是 React Hooks
那 React Hooks 又是什么呢?
看上去和 WebHook 区别不小,实际上也不小,怎么解释呢?
React Hook 在 Function Component 上开了一些 Hook,通过内置的一些 Hook 可以让 Function Component 拥有自己的 state 和 生命周期,同时可以在 Hook 中操作它。此外通过组合内置的 Hook + 自己的业务逻辑 就可以生成新的 Custom Hook,以方便优雅复用一些业务逻辑。
三、Hooks 之前的一些问题
3.1 不同组件间逻辑复用问题
很多时候,视图表现不同的组件都期望拥有一部分相同的逻辑,比如:强制登录、注入一些值等。这个时候我们经常会使用 HOC、renderProps 来包装这层逻辑,总的来说都会加入一层 wrapper,使组件的层级发生了变化,随着业务逻辑复杂度的增加,都会产生 wrapper 地狱的问题。
3.2 复杂组件阅读困难问题
随着业务逻辑复杂度的增加,我们的组件经常会在一个生命周期中干多见事,比如:在 componentDidMount 中请求数据、发送埋点等。总之就是在一个生命周期中会写入多个完全不相关的代码,进而造成各种成本的隐形增加。
假如因为这些问题再把组件继续抽象,不仅工作量比较繁杂,同时也会遇到 wrapper 地狱和调试阅读更加困难的问题。
3.3 class component 遇到的一些问题
从人的角度上讲,class component 需要关心 this 指向等,大多经常在使用 function component 还是 class component 上感到困惑;从机器的角度上讲,class component 编译体积过大,热重载不稳定
四、Hooks 有哪些功能
上述提到的三个问题,Hooks 某种意义上都做了自己的解答,那是如何解答的呢?
目前 Hooks 有: State Hook、Effect Hook、Context Hook、以及 Custom Hook。
4.1 State Hook
useState
是 State Hook 的 API。入参是initialState
,返回一个 数组,第一值是 state,第二个值是改变 state 的函数。如果
initialState
的提高需要消耗大量的计算力,同时不期望这些计算阻塞后面要干的事情的话。initialState
可以是个函数,会在 render 前调用达到 Lazy Calc 的效果。同时为了避免不必要的性能开销,在设置 State 的时候如果两个值是相等的,则也不会触发 rerender。(判断两个值相等使用的是 Object.is)
4.2 Effect Hook
useEffect
相当于 class Component 中componentDidMount
、componentDidUpdate
、componentWillUnmount
三个生命周期的大综合,在组件挂载、更新、卸载的时候都会执行 effect 里面的函数。用 “after render” 理解是最好的。值的注意的是,Effect Hook 中的内容不会像
componentDidMount
、componentDidUpdate
一样阻塞渲染。如果不期望这种表现,可是用来 API 表现一样的useLayoutEffect
。(常见的计算器快速增加问题)在一个 Function Component 里,和 useState 一样可以可以使用多次 useEffect,这样在组织业务逻辑的时候,就可以按照业务逻辑去划分代码片段了(而不是 Class Component 中只能按照生命周期去划分代码片段)。
Effect Hook 的执行实际是 “after render”,为了避免每个 render 都执行所有的 Effect Hook,
useEffect
提供了第二个入参(是个数组),组件 rerender 后数组中的值发生了变化后才会执行该 Effect Hook,如果传的是个空数组,则只会在组件第一次 Mount 后和 Unmount 前调用。这层优化理论上是可以在编译时去做的,React Team 后期可能会做掉这层。4.3 Custom Hook
useFriendStatus
就是一个典型的 Custom Hook,他利用 useState 和 useEffect 封装了 订阅朋友列表,设置朋友状态,组件卸载时取消订阅 的系列操作,最后返回一个表示是否在线的 state;在使用的时候,就可以像内置 Hook 一样使用,享用封装的系列逻辑。在 Hooks 内部,即使在一个 Function Component 中,每个 Hooks 调用都有自己的隔离空间,能保证不同的调用之间互不干扰。
useFriendStatus
以use
开头是 React Hooks 的约定,这样的话方便标识他是一个 Hook,同时 eslint 插件也会去识别这种写法,以防产生不必要的麻烦。同时如果
useXXX
的initialState
是个变量,然后这个变量发生变化的时候,该 Hook 会自动取消之前的消息订阅,重新进行 Hooks 的挂载。也就是说,在上面的例子中,如果 props.friend.id 发生变化,useFriendStatus 这个 Hooks 会重新挂载,进而 online 状态也会正常显示。4.4 Context Hook
useContext 的入参是某个 Provider 提供的 context,如果 context 发生变化的话,返回值也会立即发生变化。
4.5 Reducer Hook
如果 State 的变化有比较复杂的状态流转,可以使用 useReducer 让他更加 Redux 化,以便让这层逻辑更加清晰。同时 Reduce Hook 也提供 Lazy Calc 的功能,有需求的时候可以去使用它。
除此之外,内置的 Hooks 还有
useCallback
,useMemo
,useRef
,useImperativeHandle
,useLayoutEffect
,useDebugValue
。可以去 这里 了解更多的用法。五、例子对比
该例子是 Dan 在 React Conf 上的例子,算是非常有代表性的了:
视频地址:React Today and Tomorrow and 90% Cleaner React With Hooks
同时这里有一些 Hooks,看看实现和所解决的问题能更深的去了解 Hooks 的魅力:react hooks codesandbox
六、到底什么时候会用到 React Hook
七、引入的问题
7.1 奇怪的 useEffect
useEffect 可以覆盖 componentDidMount,ComponentDidUpdate 和 componentWillUnmount 三个生命周期的操作,但从某种意义上说,实现 componentWillUnMount 的操作是有点让人窒息的,如果是一个没看过文档的人,绝对不知道要这么操作。
7.2 底层实现导致逻辑上的问题
React Hook 在内部实现上是使用 xxx,因为使用 React Hook 有两个限制条件
React Team 为此增加了 eslint 插件:eslint-plugin-react-hooks,算是变通的去解决问题吧。
八、常见疑问
8.1 为什么 useState 返回的是个数组
这里的数组,从某种意义上说叫 tuple 会更加容器理解一些,可惜 JavaScript 中目前还没这种概念。
同时,如果返回的是个 Object 又会怎么样呢?
看起来好像是更糟糕的。
8.2 性能问题
shouldComponentUpdate
使用useMemo
即可,参考:How to memoize calculations?。同时 Hooks 的写法避免的 Class Component 创建时大量创建示例和绑定事件处理的开销,外加用 Hooks 的写法可以避免 HOC 或者 renderProps 产生深层嵌套,对 React 来说处理起来会更加轻松。
8.3 能覆盖 Class Component 的所有生命周期么?
getSnapshotBeforeUpdate
和componentDidCatch
目前覆盖不到8.4 prevState 如何获取
借助 useRef 获取
如果有其他问题,不妨去 React Hooks FAQ 看看,大概率里面涵盖了你想知道的问题。
九、参考资料
The text was updated successfully, but these errors were encountered: