React中类组件与函数组件
类组件
React 16.8+的生命周期分为三个阶段,分别是挂载阶段、更新阶段、卸载阶段。
react16之后有三个生命周期被废除(但并未删除),保留了前缀UNSAVE_的三个函数,目的是为了向下兼容。如果不使用前缀浏览器控制台会收到信息警告
废弃三个
UNSAVE_componentWillMount
UNSAVE_componentWillReceiveProps
UNSAVE_componentWillUpdate
新增两个
getDerivedStateFromProps
getSnapshotBeforeUpdate
延伸问题:为什么要删除这三个函数?
废弃的三个函数都是在render之前,因为fiber的出现,很可能因为高优先级任务的出现而打断现有任务导致它们会被执行多次。
-
componentWillMount
在ssr中 这个方法将会被多次调用, 所以会重复触发多遍,同时在这里如果绑定事件,将无法解绑,导致内存泄漏 , 变得不够安全高效逐步废弃。 -
componentWillReceiveProps
外部组件多次频繁更新传入多次不同的 props,会导致不必要的异步请求 -
componetWillupdate
更新前记录 DOM 状态, 可能会做一些处理,与componentDidUpdate相隔时间如果过长, 会导致 状态不太信
类组件生命周期第一阶段(挂载/初始化阶段):
-
constructor
:构造函数,最新被执行,我们通常在构造函数里初始化state对象或者给自定义方法绑定this。 -
componentWillMount(UNSAFE_componentWillMount)
:调用在constructor之后,在render之前,在这方法里的代码调用setState方法不会触发重渲染,所以它一般不会用来作加载数据之用,它也很少被使用到。(已弃用,建议使用componentDidMount) -
render
:render函数是纯函数,只返回需要渲染的东西,不应该包含其它业务逻辑,可以返回原生的DOM、React组件、Fragment、Portals、字符串和数字、Boolean和null等内容 -
componentDidMount
:组件装载之后调用,此时我们可以获取到DOM节点并操作,比如对canvas,svg的操作,服务器请求,订阅都可以写在这个里面,但是记得在componentWillUnmount中取消订阅
类组件生命周期第二阶段(更新阶段):
-
componentWillReceiveProps(UNSAFE_componentWillReceiveProps)
:父组件修改属性触发(已弃用) -
getDerivedStateFromProps(static getDerivedStateFromProps):是静态方法,当接收到新的属性想去修改state时可以使用(新增生命周期,替代componentWillReceiveProps)
-
shouldComponentUpdate(nextProps,nextState)
:有两个参数nectProps和nectState,表示新的属性和变化之后的state,返回一个布尔值,true表示会触发重新渲染,false表示不会触发重新渲染,默认返回true,我们通常利用此声明周期来优化React程序性能 -
componentWillUpdate(UNSAFE_componentWillUpdate)
:组件被移除前执行(已弃用) -
render
:更新阶段也会触发此生命周期 -
getSnapshotBeforeUpdate(prevProps,prevState)
:有两个参数prevProps,prevState,表示之前的属性和之前的state,这个函数有一个返回值,会作为第三个参数传给componentDidUpdate,如果你不想要返回值,可以返回null,此生命周期必须与componentDidUpdate搭配使用(新增生命周期) -
componentDidUpdate(prevProps,prevState,snapshot)
:该方法有三个参数prevPropsprevState,snapshot,表示之前的props,之前的state,和snapshot。第三个参数是getSnapshotBeforeUpdate返回的,如果触发某些回调函数时需要用到DOM元素的状态,则将对比或计算过程迁移至getSnapshotBeforeUpdate,然后再componentDidUpdate中统一触发回调或更新状态。
类组件生命周期第三阶段(卸载阶段):
-
componentWillUnmount
:当组件被卸载或者销毁了就回调用,我们可以在这个函数里去清楚一些定时器,取消网络请求,清理无效的DOM元素等垃圾清理工作
react性能优化方案
shouldComponentUpdate
控制组件自身或者子组件是否需要更新,尤其在子组件非常多的情况下, 需要进行优化。
PureComponent
PureComponent会帮你 比较新props 跟 旧的props, 新的state和老的state(值相等,或者对象含有相同的属性、且属性值相等 ),决定shouldcomponentUpdate 返回true 或者false, 从而决定要不要呼叫 render function。
注意
如果你的 state 或 props 『永远都会变』,那 PureComponent 并不会比较快,因为shallowEqual 也需要花时间。
export default class MyComponent extends Component {
// ======= 挂载卸载阶段
constructor(props: any) {
super(props);
this.state = {
name: 'Hello World',
};
}
// 16.8 新增钩子函数
static getDerivedStateFromProps(props, state) {
console.log('判断数据是否需要更新', props, state);
return null;
}
// 16.8 已废弃
componentWillMount() {
console.log('渲染之前');
}
// 16.8 componentWillMount变更,后续可能会废弃
UNSAFE_componentWillMount() {
console.log('渲染之前');
}
render() {
console.log('渲染');
return <div>MyComponent</div>;
}
componentDidMount() {
console.log('渲染完成');
}
// ==========更新阶段,也会重新调用getDerivedStateFromProps
// 16.8 新增钩子函数
static getDerivedStateFromProps(props, state) {
console.log('判断数据是否需要更新', props, state);
return null;
}
// 16.8已废弃
componentWillReceiveProps(nextProps) {
console.log('接收到来自父组件的数据', nextProps);
}
// 16.8 componentWillReceiveProps变更而来
UNSAFE_componentWillReceiveProps(nextProps) {
console.log('接收到来自父组件的数据', nextProps);
}
// 16.8新增钩子函数
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('返回组件更新daom', prevProps, prevState);
}
shouldComponentUpdate(nextProps, nextState) {
console.log('判断数据是否更新true,false来判断', nextProps, nextState);
return false;
}
// 16.8已废弃
componentWillUpdate(nextProps, nextState) {
console.log('组件数据将要更新', nextProps, nextState);
}
// 16.8 变更componentWillUpdate
UNSAFE_componentWillUpdate(nextProps, nextState) {
console.log('组件数据将要更新', nextProps, nextState);
}
componentDidUpdate(prevProps) {
console.log('组件数据更新完毕', prevProps);
}
// ========= 卸载阶段
componentWillUnmount() {
console.log('已经销毁');
}
// 其他api
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染可以显示降级 UI
return { hasError: true };
}
componentDidCatch(error, info) {
// 捕获错误信息
}
// 增加错误信息校验
render() {
if (this.state.hasError) {
// 你可以渲染任何自定义的降级 UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
函数组件
在 React 16.8 之前,函数组件的本质是函数,没有 state 的概念的,因此不存在生命周期一说,仅仅是一个 render 函数而已。
但是引入 Hooks 之后就变得不同了,它能让组件在不使用 class 的情况下拥有 state,所以就有了生命周期的概念,所谓的生命周期其实就是useState
useEffect()
useLayoutEffect()
即:Hooks 组件(使用了Hooks的函数组件)有生命周期,而函数组件(未使用Hooks的函数组件)是没有生命周期的
下面,是具体的 class 与 Hooks 的生命周期对应关系:
class 组件 | Hooks 组件 |
---|---|
constructor | useState |
getDerivedStateFromProps | useState 里面 update 函数 |
shouldComponentUpdate | useMemo |
render | 函数本身 |
componentDidMount | useEffect |
componentDidUpdate | useEffect |
componentWillUnmount | useEffect 里面返回的函数 |
componentDidCatch | 无 |
getDerivedStateFromError | 无 |
1、useState(保存组件状态)
和class的state类似,只不过是独立管理组件变量,
2、useMemo(记忆组件)
useCallback 的功能完全可以由 useMemo 所取代,如果你想通过使用 useMemo 返回一个记忆函数也是完全可以
的。
组件Dom节点,进行计算一些,包括要渲染的Dom或者数据,根据依赖参数进行更新,跟Vue中的computed类似,可用来性能优化
3、useEffect(处理副作用)和useLayoutEffect(同步执行副作用)
hooks的组件生命周期其实就是钩子函数useEffect的不同用法,传递参数的不同会导致不同的结果
useEffect和useLayoutEffect区别:简单来说就是调用时机不同, useLayoutEffect 和原来 componentDidMount & componentDidUpdate 一致,在react完成DOM更新后马上同步调用的代码,会阻塞页面渲染。而 useEffect 是会在整个页面渲染完才会调用的(官方建议优先使用 useEffect)
4、useCallBack(记忆函数)
防止因为组件重新渲染,导致方法被重新创建 ,起到缓存作用; 只有第二个参数 变化了,才重新声明一次
一个钩子函数,通过包裹我们的普通函数进行性能优化;
5、useRef(保存引用值)
useRef 用来获取DOM元素对象,也可用来保存数据
6、useReducer和useContext(减少组件层级)
useReducer 可以和 useContext 配合使用。
兄弟之间的组件传值可以使用usecontext这个hook来解决,并且可以用useReducer管理包含多个子值的 state 对象。(模拟一个小型redux场景,而无法替代redux)。
useContext可以帮助我们跨越组件层级直接传递变量,实现数据共享。就是对它所包含的组件树提供全局共享数据的一种技术。
useReducer是useState的替代方案。它接收一个形如 (state, action) => newState
的 reducer,并返回当前的 state 以及与其配套的 dispatch
方法。
7、自定义hooks
当我们想在两个函数之间共享逻辑的时候,我们就会把它提取到第三个函数中。
必须以“use”开头吗?必须如此。这个约定非常重要。不遵循的话,由于无法判断某个函数是否包含对其内部 Hook的调用,React 将无法自动检查你的 Hook 是否违反了 Hook 的规则
import React, { useState, useMemo, useEffect, useCallback } from 'react'
export default function MyComponent (props) =>{
const [name,setName] = useState('name');
useMemo(() => ()=>{
console.log('组件dom节点没有渲染之前调用一次');
}, []);
const renderDom = useMemo(() => ()=>{
console.log('组件dom节点没有渲染之前根据依赖参数props调用');
}, [props])
useEffect(() => {
console.log('组件初始化调用一次');
}, [])
useEffect(()=>{
console.log('组件根据依赖参数props更新调用');
},[props])
useEffect(()=>{
return ()=>{
console.log('组件卸载调用');
}
},[]);
const handleClick = useCallback(() =>{
console.log('监听事件通过钩子函数包裹,优化性能');
},[]);
return (
console.log('返回dom节点');
)
};