开源不易,感激你的反对,❤ star concent^_^

序言

在react利用里,存在一个顶层组件,该组件的生命周期很长,除了人为的调用unmountComponentAtNode接口来卸载掉它和用户敞开掉浏览器tab页窗口,该顶层组件是不会有被销毁的机会的,它始终随同着整个利用,所以咱们都会在该组件的componentDidMount函数里发动一些申请来获取服务器端的配置型数据并缓存起来,不便整个利用全局应用。

对于由路由零碎挂载的页面组件,咱们通常也会在它的componentDidMount函数里发动申请来获取该页面,如果状态是由store治理的(如redux、或者mobx),若须要在页面组件的卸载的时候清理相应的store状态,则还会抉择在componentWillUnmount里调用相应的办法做清理。

当然了,对于函数组件来说应用useEffect钩子函数做起来就一步到位,比起类组件显得更简略

function PageComp(){  useEffect(()=>{    /** 等效于 componentDidMount 发动申请调用 */    return ()=>{      /** 等效于 componentWillUnmount 做相应的清理 */    }  }, [])}

以后生命周期函数的应用体验

那本文题目提到的毁灭生命周期又作何解释呢?看起来没有了它们咱们是无奈实现相似需要的,在对此作出解释之前,咱们先列举一下当初的生命周期的应用体验问题。

无奈共用一套逻辑

类组件和函数组件是无奈做到0批改共用一套逻辑的,类组件在将来的很长一段时间内都将始终存在,这是咱们无奈防止的问题,但类组件和函数组件的设计理念导致它们的生命周期函数应用形式是齐全不同的,所以共享逻辑须要肯定的革新

初始化流程和组件耦合在一起

已晋升到store的状态的初始化流程却还是和组件耦合在一起,这一点肯定要留神一个前提,就是咱们通常在顶层组件的生命周期函数里实现store的某个节点的状态初始化,不论是根组件还是页面组件,它们都具备顶层组件的性质,然而把store某节点的状态初始化流程写在组件里会带来一些额定的问题,

  • 如果另一个页面组件也须要应用该节点数据时,须要额定的查看状态有没有初始化好
  • 当重构顶层组件的时候要小心翼翼的保护好这些申明周期逻辑

接下里让咱们看看在concent里是如何解决这些问题并毁灭掉生命周期函数的呢。

应用组合api对立逻辑

尽管类组件和函数的生命周期申明形式和应用形式齐全不一样,然而咱们能够依附组合api来抹掉这层差别,达到让类组件和函数组件都真正的只充当ui载体的目标

假如有以下两个自治理状态的组件,他们都具备雷同的性能,一个是类组件

class ClsPageComp extends React.Component{  state = {    list: [],    page: 1,  };  componentDidMount(){    fetchData();  }  componentWillUnmount(){    /** clear up */  }  fetchData = () => {    const { page } = this.state;    fetch('xxxx', { page }).then(list => this.setState({ list }))  }  nextPage = () => {    this.setState({ page: this.page + 1 }, this.fetchData);  }  render() {    /** ui logic */  }}

一个是函数组件

// 函数组件function PageComp() {  const [list, setList] = useState([]);  const [page, setPage] = useState(1);  const pageRef = useRef(page);  pageRef.current = page;  const fetchData = (page) => {    // fetch("xxxx", { page }).then((list) => setList(list));  };  const nextPage = () => {    const p = page + 1;    setPage(p);    fetchData(p);  };  useEffect(() => {    fetchData(pageRef.current);    return () => {      /** clear up */    };  }, []);  /** ui logic */}

两者看起来完完全全不一样,且函数组件里为了打消useEffect依赖缺失正告还是用useRef来固定住目标值,这些比拟烧脑的操作对于新用户来说是十分大的阻碍。

接下来咱们看看基于setup的组合api如何来解除这些阻碍,setup是一个一般的函数,仅提供一个参数代表以后的渲染上下文,并反对返回一个新的对象(通常都是一堆办法汇合),该对象可能通过settings在渲染块内获取到,拆卸了setup函数的组件在实例化时,仅被触发执行一次,所以咱们能够看看上述示例革新后,会变为:

function setup(ctx) {  const { initState, setState, state, effect } = ctx;  initState({ list: [], page: 0 });  const fetchData = (page) => {    fetch('xxxx', { page }).then(list => setState({ list }))  };  effect(()=>{    fetchData(state.page);    return ()=>{       /** clear up */    };  }, []);  return {    nextPage: () => {      const p = page + 1;      setState({ page: p });      fetchData(p);    }  };}

接着在类组件里和函数组件里,都可通过渲染上下文ctx拿到数据和办法

import { register, useConcent } from 'concent';@register({ setup })class ClsComp extends React.Component {  render() {    const { state: { page, list }, settings: { nextPage } } = this.ctx;    // ui logic  }}function PageComp() {  const {    state: { page, list }, settings: { nextPage },  } = useConcent({ setup });  // ui logic}

应用lifecyle打消生命周期

当咱们的页面组件状态晋升到模块里时,咱们能够应用lifecyle.mountedlifecyle.willUnmount来彻底解耦生命周期和组件的关系了,concent外部会保护一个模块对应下的实例计数器,所以依附这个性能能够准确管制模块状态的初始化机会了。

lifecyle.mounted

以后模块的第一个实例挂载结束时触发,且仅触发一次,即当该模块的所有实例都销毁后,再次有一个实例挂载结束,也不会触发了

run({  product: {     lifecycle: {      mounted: (dispatch)=> dispatch('initState')    }    }})

如需重复触发,即只有满足模块的实例数从0到1时就触发,返回false即可

lifecyle.willUnmount

以后模块的最初一个实例将销毁时触发,且仅触发一次,即当该模块再次生成了很多实例,而后又全副销毁,也不会触发了

run({  counter: {     lifecycle: {      willUnmount: dispatch=> dispatch('clearModuleState'),    }    }})

同样的如需重复触发,即只有满足模块的实例数从有变为0时就触发,返回false即可

lifecyle.loaded

如果该模块的状态和有无组件挂载无关系,则间接配置loaded即可

run({  counter: {     lifecycle: {      loaded: (dispatch)=> dispatch('initState'),    }    }})

革新示例

介绍完lifecyle,咱们来看看革新上述函数组件和类组件后的实例长为什么样,首先咱们定义product模块

import { run } from 'concent';run({  product: {    state: { list: [], page: 1 },    reducer: {      async initState() {        /** init state logic */      },      clearState() {        /** clear state logic */      },      async nextPage(payload, moduleState, ac) {        const p = moduleState.page + 1;        await ac.setState({ paeg: p });        const list = await fetch('xxxx', { page: p });        return { list };      }    },    lifecycle: {      mounted: dispatch => dispatch('initState'),      willUnmount: dispatch => dispatch('clearState'),    }  }});

接着咱们注册组件属于product模块即可,组件实例就能够调用product模块的办法和读取它的数据了。

import { register, useConcent } from 'concent';@register({ module: 'product' })class ClsComp extends React.Component {  render() {    const { state: { page, list }, mr: { nextPage } } = this.ctx;    // ui logic  }}function PageComp() {  const {    state: { page, list }, mr: { nextPage },  } = useConcent({ module: 'product' });  // ui logic}

咱们能够看到此时已没有了setup,是因为咱们不须要额定定义方法和数据了,当咱们须要为组件定义一些非模块的办法和数据时,仍然能够定义setup

function setup(ctx) {  const { initState, setState, state, effect } = ctx;  initState({ xxxx: 'hey i am private' });  effect(()=>{   // 等效于useEffect里,当xxxx扭转时执行此副作用   console.log(state.xxxx);  }, ['xxxx']);  return {    changeXXX: (e)=> setState({xxxx: e.target.value}),  };}

而后组件拆卸setup即可

import { register, useConcent } from 'concent';@register({ module: 'product', setup })class ClsComp extends React.Component {  render() {    const { state: { page, list }, mr: { nextPage }, settings } = this.ctx;    // ui logic  }}function PageComp() {  const {    state: { page, list }, mr: { nextPage }, settings,  } = useConcent({ module: 'product', setup });  // ui logic}

结语

综上所述,咱们能够看到其实并没有毁灭生命周期函数,而是转移并对立了生命周期函数的定义入口,让其和组件的定义彻底拆散,这样无论咱们怎么重构组件代码,都不怕动到整个模块状态的初始化流程。

附录

和本期主题相近的其余文章

  • 初识组合api
  • recoil vs concent

CloudBase CMS

欢送小哥哥们来撩CloudBase CMS ,打造一站式云端内容管理系统,它是云开发推出的,基于 Node.js 的 Headless 内容治理平台,提供了丰盛的内容治理性能,安装简单,易于二次开发,并与云开发的生态体系紧密结合,助力开发者晋升开发效率。

concent已为其治理后盾提供强力反对,新版的治理界面更加好看和体贴了。

FFCreator

也欢送小哥哥们来撩FFCreator,它是一个基于node.js的轻量、灵便的短视频加工库。您只须要增加几张图片或视频片段再加一段背景音乐,就能够疾速生成一个很酷的视频短片。

FFCreator是一种轻量又简略的解决方案,只须要很少的依赖和较低的机器配置就能够疾速开始工作。并且它模仿实现了animate.css90%的动画成果,您能够轻松地把 web 页面端的动画成果转为视频,真的很给力。