不同框架组件化之间的对比 之 lifecycle

786 阅读9分钟

锉其兑,解其纷,和其光,同其尘 --- 《道德经》第四章 和光同尘

  1. React 生命周期 16/15
  2. Vue 2/3
  3. Angular
  4. Svelte

浏览加载

理解 web 页面的生命周期,就是在合适的时候做合适的事情,让事情合理, 和光同尘。

  • Script 脚本
  • DOMContentLoaded (此时其实 dom 结果已经完成,可以操作 dom, 资源文件可能还没有加载文件,异步的 js 还没有执行完毕)
    • image onload
    • iframe onload
    • readyState: complete
  • onload 所有资源加载完毕
  • beforeunload 事件在用户准备离开页面,在 unload 事件之前触发
  • unload 用户最终离开时会触发该事件

一般来说,大多数的操作我们都应该放在 DOMContentLoaded 事件中执行,而不要放在 window.onload 中执行。

文档加载状态

document.readyState 可以返回文档的加载状态,文档的加载状态有一下四种:

  • uninitialized - 还未开始载入
  • loading - 载入中
  • interactive - 已加载,文档与用户可以开始交互
  • complete - 载入完成

它对应的有一个事件 readystatechange, 我们可以通过这个事件来监听文件的变化。

document.addEventListener("readystatechange", () => {
  console.log(document.readyState);
});

document.addEventListener("onload", () => {
  console.log("资源加载完毕");
});

document.addEventListener("beforunload", () => {
  console.log("将要卸载");
});

document.addEventListener("unload", () => {
  console.log("卸载");
});

DOMContentLoaded

顾名思义,当 HTML 文档加载完毕和解析完毕之后,DOMContentLoaded 就会被触发:

document.addEventListener("DOMContentLoaded", () => {
  console.log("HTML 文档加载完毕和解析完毕");
});

需要注意的是: 无需等待样式表、图像和子框架的完成加载。

问题

从输入 url 到页面加载完毕发生了哪些事情?

React 生命周期和钩子函数

类组件生命周期

  • 挂载阶段

    • constructor(props)
    • static getDerivedStateFromProps(props, state):存在只有一个目的:让组件在 props 变化时更新 state
    • render()
    • componentDidMount()
    • UNSAFE_componentWillMount() 因为使用 fiber 结构,UNSAFE_componentWillMount 已经不适合在新的组件中使用了
  • 更新阶段

    • 触发组件更新的三种方式:newProps, newState, forceUpdate
    • static getDerivedStateFromProps()
    • shouldComponentUpdate(nextProps, nextState)
    • render()
    • getSnapshotBeforeUpdate(prevProps, prevState)
      • 通过 Ref 获取 dom
    • componentDidUpdate(prevProps, prevState, snapshot)
    • 不安全的生命周期
      • UNSAFE_componentWillUpdate()
      • UNSAFE_componentWillReceiveProps()
  • 卸载阶段

    • componentWillUnmount()
  • 处理错误

    • static getDerivedStateFromError(error)
    • componentDidCatch(error, info)

每一个阶段在 React 16 中又分为 3 个期间

  1. 渲染期
  2. 预提交期
  3. 提交期

注意

静态方法: getDerivedStateFromProps, 其实很简单,就是要在组件渲染之前,可以根据外部数据 props 修改数据 state。

  • 必须要有返回值,返回决定了 state 的状态
  • 可以返回 null 表示没有任何修改
  • 不能访问 this 此时 this 是 undefined ,这能是他能修改 state 的真实原因,不能调用 setState 方法.

shouldComponentUpdate 方法

可以可以决定组件是否真正的更新,如果 shouldComponentUpdate 返回值为 true, 则重新执行 render 函数,更新。返回 false 则反之。

getSnapshotBeforeUpdate(prevProps, prevState)

需要返回值 Snapshot 或者 null,任何返回值将作为参数传递给 componentDidUpdate()。

componentDidUpdate(prevProps, prevState, snapshot)

  • 一个简单的示例
import React from "react";

// components
import Layout from "../components/layout";

class Lifecycle extends React.Component {
  constructor(props) {
    console.log("constructor`s:", props);
    // super(props)
    this.state = { a: 1 };
    this.handleClick = this.handleClick.bind(this);
  }

  static getDerivedStateFromProps(props, state) {
    console.log("getDerivedStateFromProps:props", props);
    console.log("getDerivedStateFromProps:state", state);
    // 可以返回一个新的 state 对象, getDerivedStateFromProp 在 render 函数之前执行,
    // 在进行方法中不能通过 this 访问 setState, 此时的 this 为 undefined
    return null;
  }

  shouldComponentUpdate(nextProps, nextState) {
    console.log("shouldComponentUpdate nextState", nextProps);
    console.log("shouldComponentUpdate nextState", nextState);
    return true;
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log("getSnapshotBeforeUpdate", prevState);
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log("componentDidUpdate", prevProps);
    console.log("componentDidUpdate", prevState);
    console.log("componentDidUpdate", snapshot);
  }

  handleClick() {
    this.setState({
      a: this.state.a + 1
    });
  }
  render() {
    return (
      <Layout>
        <div>{this.state.a}</div>
        <div>{this.state.b}</div>
        <button onClick={this.handleClick}>+1</button>
        <div>132</div>
      </Layout>
    );
  }
}

export default Lifecycle;

hooks

hooks 主要是解决函数组件,不能使用 state 的能力,同时函数式编程有强大的组合能力,使得代码更加容易维护

  1. state
  2. props
  3. 钩子函数取代生命周期
  • 一个简单的示例:
import React from "react";

// components
import Layout from "../components/layout";

const Transition = () => {
  const [style, setStyle] = React.useState({
    background: "blue"
  });

  const onHandleTransition = () => {
    if (style.background === "blue") {
      setStyle({
        background: "red",
        transition: "all 1s linear .2s",
        color: "white"
      });
    } else {
      setStyle({
        background: "blue",
        transition: "all 1s linear .2s",
        color: "orange"
      });
    }
  };

  return (
    <Layout>
      <div style={style}>123</div>
      <button onClick={onHandleTransition}>点击触发过渡</button>
    </Layout>
  );
};

export default Transition;

特点

  • 没有 this
  • 使用函数式编程
  • 没有生命周期,部分功能交给了函数处理副作用 useEffect

Vue 生命周期

Vue2 生命周期

Vue2 生命周期与 Vue 实例紧密结合:

  • 创建期
    • beforeCreate
    • created (开始能访问 this, 也就是 Vue 实例,因为此时 Vue 的初始化 init 工作才完成)
  • 挂载
    • beforeMount
    • mounted (开始能访问 dom, 此时 dom 挂载完毕)
  • 更新期
    • beforeUpdate
    • updated
  • 卸载期
    • beforeDestroy
    • destroyed

我们看到 Vue2 的生命周期是非常清晰的。有两个重要的时间点:

  • created, 是 Vue 初始化完毕,能够开始访问 Vue 组件实例
  • mounted dom 挂载完毕,能处理 dom 以及其他的副作用

Vue3 生命周期钩子

Vue3 在生命周期钩子上发生了变化,vue3的生命周期函数,可以按需导入到组件中,且只能在 setup() 函数中使用:

beforeCreate -> 使用setup() created -> 使用 setup() beforeMount -> onBeforeMount mounted -> onMounted beforeUpdate -> onBeforeUpdate updated -> onUpdated beforeDestroy -> onBeforeUnmount destroyed -> onUnmounted errorCaptured -> onErrorCaptured

  • 简单示例
import { onMounted, onUpdated } from 'vue'
 
export default {
    setup() {
        onMounted(() => {
        console.log('on mounted')
        })
        onUpdated(() => {
        console.log('on updated')
        })
    }
}

小结

Vue2 与 Vue3 的生命周期在用法上有很大的变化,需要配合 setup 函数,进行使用。

keep-alive 组件组件独享生命周期钩子

本质:keep-alive 是用来缓存 vm 实例的,具有三个 props 和两个周期钩子函数

  • props:include - 字符串或正则表达式。只有名称匹配的组件会被缓存。

  • props:exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。

  • props:max - 数字。最多可以缓存多少组件实例。

  • activated 被 keep-alive 缓存的组件激活时调用。

  • deactivated 被 keep-alive 缓存的组件停用时调用。

transiton 组件 也有钩子函数

  • before-enter
  • before-leave
  • before-appear
  • enter
  • leave
  • appear
  • after-enter
  • after-leave
  • after-appear
  • enter-cancelled
  • leave-cancelled (v-show only)
  • appear-cancelled

当我们需要灵活的控制 Vue 的过渡组件的效果的时候,可以使用 transition 组件提供的钩子函数

Vue-Router 生命周期钩子或者守卫

Vue-Router 也提供可很多生命周期钩子函数:

钩子与守卫的区别: 钩子函数不接受 next 方法,没有守卫的能力。

  • 全局钩子函数
    • beforeEach 全局前置守卫
    • beforeResolve 全局解析守卫
    • afterEach 全局后置钩子 钩子不会接受 next
  • 路由配置独享守卫
    • beforeEnter
  • 组件内部的路由守卫钩子
    • beforeRouteEnter
    • beforeRouteUpdate (2.2 新增)
    • beforeRouteLeave

也就是说只有一个 afterEach 后置钩子:其他的六个守卫,有 next 方法。

一个路由切换,钩子函数的调用:

  1. 触发当前组件离开行为或者事件
  2. 当前组件钩子函数 beforeRouteLeave 被触发,表示在路由离开之前,调用的生命周期钩子函数
  3. 全局前置钩子响应,知道要进行路由变化了,beforeEach 进行调用。
  4. 将要进入的组件的路由守卫 beforeRouteUpdate 将要调用 从将离开的组件,进入将要激活的组件之间的一次互动
  5. 在将要激活的组件的钩子函数执行之后,机会进入路由独享守卫,看看路由有没有要干的事情
  6. 解析异步组件
  7. 在激活的组件中调用 beforeRouteEnter,说明从这个时候一个开始进入新的组了
  8. 此时全局的解析钩子开始工作了,调用 beforeResolve
  9. 解析完毕之后导航被确定下来
  10. 定下来之后调用全局后置钩子 afterEach
  11. 触发 DOM 更新
  12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入

关于路由有一个重要的点: 记住参数或查询的改变并不会触发进入/离开的导航守卫.你可以通过观察 $route 对象来应对这些变化,或使用 beforeRouteUpdate 的组件内守卫。还可以绑定一个特别的 key>

Angular

Angular 指令的生命周期,它是用来记录指令从创建、应用及销毁的过程。

生命周期

  • ngOnChanges() 初始化输入属性 ,Angular(重新)设置数据绑定输入属性时的响应。
  • ngOnInit() 初始化其他属性,在 Angular 首次显示数据绑定属性并设置指令/组件的输入属性后初始化指令/组件。在第一次之后 调用一次 ngOnChanges() 。
  • ngDoCheck() 组件变更检测,检测 Angular 无法或不会自行检测的更改并采取相应措施。在每次更改检测运行期间,在 ngOnChanges() 和之后立即调用 ngOnInit()。
  • ngAfterContentInit() 投影内容初始化,在 Angular 将外部内容投影到组件的视图/指令所在的视图后进行响应。在第一次之后 调用一次
  • ngAfterContentChecked() 针对投影内容的变更检测在 Angular 检查投射到指令/组件中的内容后响应。在 ngAfterContentInit() 随后和随后的每一次调用之后 ngDoCheck()。
  • ngAfterViewInit() 初始化完组件视图及其子视图之后调用 ,在 Angular 初始化组件的视图和子视图/指令所在的视图后响应。在第一次之后 调用一次 ngAfterContentChecked()。一般进行 DOM 操作
  • ngAfterViewChecked() 每次做完组件视图和子视图的变更检测之后调用,在 Angular 检查组件的视图和子视图/指令所在的视图后响应。在 ngAfterViewInit() 随后和随后的每一次调用之后 ngAfterContentChecked()。
  • ngOnDestroy() 当 Angular 每次销毁指令/组件之前调用并清扫。 在这儿反订阅可观察对象和分离事件处理器,以防内存泄漏,就在 Angular 破坏指令/组件之前进行清理。取消订阅 Observable 并分离事件处理程序以避免内存泄漏。在 Angular 破坏指令/组件之前 调用。组件销毁时执行

1. 指令与组件共有的钩子

ngOnChanges ngOnInit ngDoCheck ngOnDestroy

2. 组件特有的钩子

ngAfterContentInit ngAfterContentChecked ngAfterViewInit ngAfterViewChecked

3. 生命周期调用顺序

ngOnChanges - 当数据绑定输入属性的值发生变化时调用 ngOnInit - 在第一次 ngOnChanges 后调用 ngDoCheck - 自定义的方法,用于检测和处理值的改变 ngAfterContentInit - 在组件内容初始化之后调用 ngAfterContentChecked - 组件每次检查内容时调用 ngAfterViewInit - 组件相应的视图初始化之后调用 ngAfterViewChecked - 组件每次检查视图时调用 ngOnDestroy - 指令销毁前调用

Svelte

Svelte 生命周期:

  • onMount 在 挂载到 dom 之后执行
  • beforeUpdate 在任何状态更改后立即在组件更新之前运行的回调。
  • afterUpdate 在组件更新后立即运行的回调。
  • onDestroy 在组件更新后立即运行的回调。 唯一一个服务器端组件内部运行
  • tick 在应用任何暂挂状态更改后解决,或者在下一个微任务中(如果没有更改)解决

由于 Svelte 没有生命周期,不要初始化一些与虚拟 dom 相关的内容,所以生命周期也特简单。

<script>
	import { onMount,beforeUpdate, afterUpdate, onDestroy } from 'svelte';

	onMount(() => {
		console.log('the component has mounted');
	});
  // 有返回值,则用于卸载是调用,类似于 React 的钩子函数 useEffect
  onMount(() => {
		const interval = setInterval(() => {
			console.log('beep');
		}, 1000);

		return () => clearInterval(interval);
	});

  beforeUpdate(() => {
		console.log('the component is about to update');
	});

  afterUpdate(() => {
		console.log('the component just updated');
	});

  onDestroy(() => {
		console.log('the component is being destroyed');
	});

  beforeUpdate(async () => {
		console.log('the component is about to update');
		await tick();
		console.log('the component just updated');
	});

</script>

todo

  1. url 从输入经历什么?
  2. 微信小程序页面生命周期,组件生命周期
  3. 更好的示例
  4. 其他