Presentational and Container Components
react团队成员之一的Dan Abramov在medium上写过一篇文章 Presentational and Container Components,他在文中将组件分为两类,分别是Presentational
和Container
。Presentational
是展示类组件,比如说Page,Sidebar,Story,List。Container
组件是功能类组件,比如UserPage, FollowersSidebar, StoryContainer, FollowedUserList。
它们是React组件的两种设计模式,和组件本身是class component还是function component关系不大。
Hooks 出现的动机
便于复用组件中的逻辑
组件中逻辑的复用,解决render props和hoc所做的事,抽离它们中的逻辑。hoc组件太多会导致标签结构混乱复杂,大多数情况下一个Container Component外面会包裹很多Presentational Component,通过Hooks,你可以从组件中抽离状态逻辑,让它们变得可以单独测试和重复使用,Hooks可以让你复用状态逻辑,而不用去变更组件的层级关系,这使得与其他组件共享Hooks变得容易,甚至可以编写社区共用的Hooks。
组件太复杂而变得难以理解
组件的生命周期中经常会混入很多不相关的逻辑,比如在componentDidMount
和componentDidUpdate
中会进行数据的请求,但是在componentDidMount
中我们可能还会进行一些事件的监听,在componentWillUnmount
中会清理这些事件,这就使得不相关逻辑的代码混在一起,而逻辑相关的代码会被拆分在两个方法里面,这会使得维护变得困难,大多数的组件不能再次拆分成更小的组件,因为逻辑遍布整个文件。很多人会选择引入一个单独的状态管理工具,但是这又将会引入一大部分抽象,并且将在各个文件中切换。
JavaScript中的class使得学习React变得困难
为了使用class取编写组件需要编写很多无意义的代码,class组件中需要去考虑this问题,在React的开发人员中,关于class组件和function组件的讨论也存在着巨大的分歧。而且对于Prepack这种提前优化工具,class变得不那么容易去优化,React团队希望能够提供一种方式,让这些逻辑代码变得可以优化。React团队建议在设计组件的时候能够向function组件靠拢,Hooks提供了能够在function组件中去编写生命周期时的逻辑,而不需要去理解复杂的响应原理。
目前React团队没有将class组件移除的计划
React Hooks简单使用
React Hooks是React提交的一系列函数,他们提供在function组件中hook state和生命周期的特性,它们不能在class组件中被使用。
State Hook
例子:
1 | import { useState } from 'react'; |
useState就是一个React Hooks,这个方法接收一个值,并返回一个数组,通过JavaScript的Array destructuring特性,可以将返回的值赋给变量。
useState会返回两个值,第一个是state,第二个是更新该state的方法,类似setState
。
一个function组件中可以使用多次useState
1 | function ExampleWithManyStates() { |
Effect Hook
例子:
1 | import { useState, useEffect } from 'react'; |
React团队提出了一种叫“side effects”的操作,比如fetch data、subscriptions、或者手动去变更DOM,因为它们会影响其他组件,并且在渲染中无法进行。useEffect
hook类似于类组件中componentDidMount
、componentDidUpdate
、componentWillUnmount
的统一。上面的例子中将会在Effect中去改变title的值。
在使用useEffect
时,只需要告诉React在更新DOM之后需要进行的“effect”,通常运行这些“effect”会在渲染之后(包括第一次渲染)。useEffect
可以通过返回一个函数来进行清理操作,这些操作会在组件unmounts时运行,或者因为后续渲染重新运行“effect”之前。下面的例子在effect中subscribe好友的状态,并且通过返回一个函数unsubscribe状态。
1 | import { useState, useEffect } from 'react'; |
跟useState
一样,在function组件中也可以多次使用useEffect
1 | function FriendStatusWithCounter(props) { |
这样可以很方便的根据逻辑代码去组织逻辑,而不是通过生命周期去组织代码。
Hooks的使用规则
Hooks是JavaScript函数,但是在使用它们时需要强制遵循两条规则规则
- 调用Hooks必须在顶层函数,不能在循环、条件和嵌套函数中调用
- 调用Hooks必须在function组件中,不能在传统的JavaScript方法中调用(也可以在自己定义的Hooks中调用)
React团队提供了相关的eslint插件,可以在eslint中检查上面的规则。linter plugin
自定义Hook
有时我们想在不同的组件中执行相同的逻辑,比如上面的subscribe好友状态,下面的例子中抽离了相关的逻辑,定义了一个useFriendStatus
的方法
1 | import { useState, useEffect } from 'react'; |
它接收一个friendID作为参数,并且返回好友在线的状态,现在可以在两个组件中使用它
1 | function FriendStatus(props) { |
1 | function FriendListItem(props) { |
这些组件的状态是完全独立的,钩子是重用有状态逻辑的一种方式,而不是状态本身。事实上,每次调用Hook都有一个完全隔离的状态,所以你甚至可以在一个组件中使用相同的自定义Hook两次。
如果一个函数需要以use
开头,在React中回叫它自定义Hooks,在lint工具中,这会是一个检查的条件。
其他Hook
有一些Hook不经常会用到,但是很有用,比如useContext
,它可以让你subscribe React context
1 | function Example() { |
useReducer
可以让你在复杂的组件中使用Reducer的方式管理state
1 | function Todos() { |
更多的Hook可以阅读React提供的API文档 Hooks API Reference