Stateless Function Component:html
const App = (props) => (
<div>Hello, {props.name}</div>
)
复制代码
Class Component:react
class App extends React.Component {
render() {
return (
<div>Hello, {this.props.name}</div>
)
}
}
复制代码
上面是两个最简单的 function component 和 class component 的对比,首先从行数上来看,3 << 7。git
再看 babel 编译成 es2015 后的代码:github
Function Component:web
"use strict";
var App = function App(props) {
return React.createElement("div", null, "Hello, ", props.name);
};
复制代码
Class Component:npm
去除了一堆 babel helper 函数数组
"use strict";
var App =
/*#__PURE__*/
function (_React$Component) {
_inherits(App, _React$Component);
function App() {
_classCallCheck(this, App);
return _possibleConstructorReturn(this, _getPrototypeOf(App).apply(this, arguments));
}
_createClass(App, [{
key: "render",
value: function render() {
return React.createElement("div", null, "Hello, ", this.props.name);
}
}]);
return App;
}(React.Component);
复制代码
Function Component 仅仅是一个普通的 JS 函数,Class Component 由于 ES2015 不支持 class
的缘由,会编译出不少和 class 相关的代码。性能优化
同时由于 Function Component 的特殊性,React 底层或许能够作 更多的性能优化。bash
总的来讲,如下点:babel
bind(this)
在 React Class Component 中,咱们必定写过不少这样的代码
class App extends Component {
constructor(props) {
super(props);
this.state = {
name: 'rccoder',
age: 22
},
this.updateName = this.updateName.bind(this);
this.updateAge = this.updateAge.bind(this);
}
render() {
<div onClick={this.updateName}
</div>
}
}
复制代码
固然这个错不在 React,而在于 JavaScript 的 this 指向问题,简单看这样的代码:
class Animate {
constructor(name) {
this.name = name;
}
getName() {
console.log(this);
console.log(this.name)
}
}
const T = new Animate('cat');
T.getName(); // `this` is Animate Instance called Cat
var P = T.getName;
P(); // `this` is undefined
复制代码
这个例子和上面的 React 一模一样,在没有 bind 的状况下这样写会 致使了 this 是 global this,即 undefined。
解决它比较好的办法就是在 contructor 里面 bind this。
在新版本的 ES 中,有 Public Class Fields Syntax 能够解决这个问题,即:
class Animate {
constructor(name) {
this.name = name;
}
getName = () => {
console.log(this);
console.log(this.name)
}
}
const T = new Animate('cat');
T.getName(); // `this` is Animate Instance called Cat
var P = T.getName;
P(); // `this` is Animate Instance called Cat
复制代码
箭头函数不会建立本身的 this,只会依照词法从本身的做用域链的上一层继承 this,从而会让这里的 this 指向刚好和咱们要的一致。
即便 public class fileds syntax 借助 arrow function 能够勉强解决这种问题,但 this 指向的问题依旧让人 “恐慌”。
React 有很是多的生命周期,在 React 的版本更新中,有新的生命周期进来,也有一些生命周期官方已经渐渐开始认为是 UNSAFE
。目前被标识为 UNSAFE
的有:
新引入了
getDerivedStateFromProps
和 getSnapshotBeforeUpdate
均是返回一个处理后的对象给 componentDidUpdate
,全部须要操做的逻辑都放在 componentDidUpdate
里面。
原则上:
getDerivedStateFromProps
+ componentDidUpdate
能够替代 componentWillReceiveProps
的全部正常功能;getSnapshotBeforeUpdate
+ componentDidUpdate
能够替代 componentWillUpdate
的全部功能。具体的 缘由 和 迁移指南 能够参考 React 的官方博客:Update on Async Rendering,有比较详实的手把手指南。
最后,你应该依旧是一样的感受,Class Component 有如此多的生命周期,显得是如此的复杂。
说了上面一堆看似和题目无关的话题,其实就是为了让你以为 “Function Component 大法好”,而后再开心的看下文。
终于来到了和 Hooks 相关的部分,首先咱们看下 什么是 Hooks:
首先来看下咱们熟知的 WebHook:
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
那 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,以方便优雅复用一些业务逻辑。
不少时候,视图表现不一样的组件都指望拥有一部分相同的逻辑,好比:强制登陆、注入一些值等。这个时候咱们常常会使用 HOC、renderProps 来包装这层逻辑,总的来讲都会加入一层 wrapper,使组件的层级发生了变化,随着业务逻辑复杂度的增长,都会产生 wrapper 地狱的问题。
随着业务逻辑复杂度的增长,咱们的组件常常会在一个生命周期中干多见事,好比:在 componentDidMount 中请求数据、发送埋点等。总之就是在一个生命周期中会写入多个彻底不相关的代码,进而形成各类成本的隐形增长。
假如由于这些问题再把组件继续抽象,不只工做量比较繁杂,同时也会遇到 wrapper 地狱和调试阅读更加困难的问题。
从人的角度上讲,class component 须要关心 this 指向等,大多常常在使用 function component 仍是 class component 上感到困惑;从机器的角度上讲,class component 编译体积过大,热重载不稳定
上述提到的三个问题,Hooks 某种意义上都作了本身的解答,那是如何解答的呢?
目前 Hooks 有: State Hook、Effect Hook、Context Hook、以及 Custom Hook。
import React, { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div>
);
}
复制代码
useState
是 State Hook 的 API。入参是 initialState
,返回一个 数组,第一值是 state,第二个值是改变 state 的函数。
若是 initialState
的提升须要消耗大量的计算力,同时不指望这些计算阻塞后面要干的事情的话。initialState
能够是个函数,会在 render 前调用达到 Lazy Calc 的效果。
useState(() => {
// ... Calc
return initialState;
})
复制代码
同时为了不没必要要的性能开销,在设置 State 的时候若是两个值是相等的,则也不会触发 rerender。(判断两个值相等使用的是 Object.is)
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
return () => {
... // Similar to componentWillUnMount。Named as clear up effect
}
});
return (
<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div>
);
}
复制代码
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 后期可能会作掉这层。
import React, { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}> {props.friend.name} </li>
);
}
复制代码
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 状态也会正常显示。
function Example() {
const locale = useContext(LocaleContext);
const theme = useContext(ThemeContext);
// ...
}
复制代码
useContext 的入参是某个 Provider 提供的 context,若是 context 发生变化的话,返回值也会当即发生变化。
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter({initialState}) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<> Count: {state.count} <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); } 复制代码
若是 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
useEffect 能够覆盖 componentDidMount,ComponentDidUpdate 和 componentWillUnmount 三个生命周期的操做,但从某种意义上说,实现 componentWillUnMount 的操做是有点让人窒息的,若是是一个没看过文档的人,绝对不知道要这么操做。
useEffect(() => {
// cDM or cDU
return () => {
// cWU
}
})
复制代码
React Hook 在内部实现上是使用 xxx,由于使用 React Hook 有两个限制条件
React Team 为此增长了 eslint 插件:eslint-plugin-react-hooks,算是变通的去解决问题吧。
这里的数组,从某种意义上说叫 tuple 会更加容器理解一些,惋惜 JavaScript 中目前还没这种概念。
同时,若是返回的是个 Object 又会怎么样呢?
let { state: name, setState: setName } = useState('Name');
let { state: surname, setState: setSurname } = useState('Surname');
复制代码
shouldComponentUpdate
使用 useMemo
便可,参考:How to memoize calculations?。
同时 Hooks 的写法避免的 Class Component 建立时大量建立示例和绑定事件处理的开销,外加用 Hooks 的写法能够避免 HOC 或者 renderProps 产生深层嵌套,对 React 来讲处理起来会更加轻松。
getSnapshotBeforeUpdate
和 componentDidCatch
目前覆盖不到
若是有其余问题,不妨去 React Hooks FQA 看看,大几率里面涵盖了你想知道的问题。
原文地址:github.com/rccoder/blo… (去这交流更方便哦~)