React函数类组件及其Hooks学习
函数类组件
函数式组件和类式组件的区别:
React组件可以分为类组件和函数式组件,两者最明显的不同就是在语法上,函数组件是一个纯函数,它接收一个props对象返回一个react元素。而类组件需要去继承React.Component并且创建render函数返回react元素,这将会要更多的代码,虽然它们实现的效果相同。
- 函数式组件中没有state
- 函数式组件中没有生命周期(重点,涉及到生命周期的方法只能在类组件中定义。)
在较新的react版本看中添加了hooks,使得我们可以在函数组件中使用useState钩子去管理state,使用useEffect钩子去使用生命周期函数。从这个改版中我们可以看出作者更加看重函数组件,而且react团队曾提及到在react之后的版本将会对函数组件的性能方面进行提升。
为什么要使用函数式组件?
- Hooks是比高阶组件(HOC)和render props更优雅的逻辑复用方式。这个是很多人喊“真香”的原因。优雅的逻辑复用方式,会促进一个更加蓬勃的生态,这对于原本生态就很强的React来说是如虎添翼。会有更多的人愿意把自己的逻辑抽离成hooks(因为真的太优雅了),发布为library,为react生态添砖加瓦(看看这段时间各种基于hooks的状态管理工具)。我还认为,很快会出现一些“hooks生态圈”的“lodash”。
- 函数式组件的心智模型更加“声明式”。hooks(主要是useEffect)取代了生命周期的概念(减少API),让开发者的代码更加“声明化”:
- 旧的思维:“我在这个生命周期要检查props.A和state.B(props和state),如果改变的话就触发xxx副作用”。这种思维在后续修改逻辑的时候很容易漏掉检查项,造成bug。
- 新的思维:“我的组件有xxx这个副作用,这个副作用依赖的数据是props.A和state.B”。从过去的命令式转变成了声明式编程。
- 其实仔细想一想,人们过去使用生命周期不就是为了判断执行副作用的时机吗?现在hooks直接给你一个声明副作用的API,使得生命周期变成了一个“底层概念”,无需开发者考虑。开发者工作在更高的抽象层次上了。
- 类似的道理,除了声明副作用的API,react还提供了声明“密集计算”的API(useMemo),取代了过去“在生命周期做dirty检查,将计算结果缓存在state里”的做法。React内核帮你维护缓存,你只需要声明数据的计算逻辑以及数据的依赖。
- 函数式组件的心智模型更加“函数式”。react团队正在循序渐进地教育社区,为未来的并发模式打下基础。(其实react从一开始就受到了很多函数式编程的影响,现在推行函数式组件算是“回归初心”)。下面我会详细讨论函数式组件的心智模型。
Hooks概念及常用的Hooks
Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。Hook 不能在 class 组件中使用 —— 这使得你不使用 class 也能使用 React。
React官网是这样描述Hooks的:Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),而并非强制按照生命周期划分。你还可以使用 reducer 来管理组件的内部状态,使其更加可预测。
1. useState
: State的Hook
State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作。
语法
const [xxx, setXxx] = React.useState(initValue)
useState()说明:
参数: 第一次初始化指定的值在内部作缓存;
返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数。
可以多次使用useState()
。
setXxx()2种写法:
setXxx(newValue)
: 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
setXxx(value => newValue)
: 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
示例
import React, { useState } from 'react';
function Example() {
// 声明一个叫 “count” 的 state 变量。
const [count, setCount] = useState(0);
// 加的回调 setXxx()第二种写法
function add() {
setCount(count => count + 1);
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me to + 1
</button>
<button onClick={() => setCount(add)}>
Click me to add 1
</button>
</div>
);
}
export default Example
2. useEffect
: 副作用的Hook
Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)。
我们之前可能已经在React组件中执行过数据获取、订阅或者手动修改过DOM。我们统一把这些操作称为“副作用”,或者简称为“作用”。它跟 class 组件中的 componentDidMount
、componentDidUpdate
和 componentWillUnmount
具有相同的用途,只不过被合并成了一个 API。
React中的副作用操作
- 发ajax请求数据获取
- 设置订阅 / 启动定时器
- 手动更改真实DOM
语法和说明:
- 通过使用这个 Hook,你可以告诉 React 组件需要在渲染后执行某些操作
- 将
useEffect
放在组件内部让我们可以在 effect 中直接访问count
state 变量(或其他 props)。 - 默认情况下,它在第一次渲染之后和每次更新之后都会执行,即不加第二个参数的情况下。
总体说明:
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
可以多次使用useEffect
。
替代componentDidMount
:
useEffect(() => {
// 在此可以执行任何带副作用操作,来初始化代码
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
替代componentDidUpdate
:
useEffect(() => {
// 在此可以执行任何带副作用操作
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行,此时不能为空,数组内的值为所监控的state的值,如果不加此参数,默认为所有的state的值
// 例如如下代码
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
替代componentWillUnmount
:
useEffect(() => {
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
})
实例:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
3. useRef
: ref的Hook
Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据,跟React.createRef()
类似。
useRef
返回一个可变的 ref 对象,其 .current
属性被初始化为传入的参数(initialValue
)。返回的 ref 对象在组件的整个生命周期内保持不变。
语法:
const refContainer = useRef()
ref 对象的 .current
属性为相应的 DOM 节点。
实例:
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
函数类组件中使用props
使用方式类似类组件的props使用,父级组件需要暴露接口给子组件,子组件才能接收,子组件接收数据的方法:直接作为函数的参数(props)传给子组件。
import React, { useState } from 'react'
import Title from "../Title"
//父级组件传过去
function Parent() {
let [count, setCount] = useState(1)
const add = (count) => { setCount(count) }
return (<div>
//类似类组件中的接口传递数据到子组件
<Title count={count} />
<button onClick={() => add(count + 1)}>+</button>
</div>)
}
export default Parent;
//子组件接收
import React from 'react'
function Title(props) {
return (
<h1>{props.count}</h1>
)
}
export default Title;