如何优雅的消灭掉react生命周期函数

字节踏雪使
• 阅读 9457

开源不易,感谢你的支持,❤ star concent^_^

如何优雅的消灭掉react生命周期函数

序言

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

对于由路由系统挂载的页面组件,我们通常也会在它的componentDidMount函数里发起请求来获取该页面,如果状态是由store管理的(如redux、或者mobx),若需要在页面组件的卸载的时候清理相应的store状态,则还会选择在componentWillUnmount里调用相应的方法做清理。

如何优雅的消灭掉react生命周期函数

当然了,对于函数组件来说使用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
}

结语

综上所述,我们可以看到其实并没有消灭生命周期函数,而是转移并统一了生命周期函数的定义入口,让其和组件的定义彻底分离,这样无论我们怎样重构组件代码,都不怕动到整个模块状态的初始化流程。

附录

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

CloudBase CMS

如何优雅的消灭掉react生命周期函数

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

concent已为其管理后台提供强力支持,新版的管理界面更加美观和体贴了。

FFCreator

如何优雅的消灭掉react生命周期函数

也欢迎小哥哥们来撩FFCreator,它是一个基于node.js的轻量、灵活的短视频加工库。您只需要添加几张图片或视频片段再加一段背景音乐,就可以快速生成一个很酷的视频短片。

FFCreator是一种轻量又简单的解决方案,只需要很少的依赖和较低的机器配置就可以快速开始工作。并且它模拟实现了animate.css90%的动画效果,您可以轻松地把 web 页面端的动画效果转为视频,真的很给力。
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
6个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
1年前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这