开源不易,感谢你的支持,❤ star concent^_^css
在react应用里,存在一个顶层组件,该组件的生命周期很长,除了人为的调用unmountComponentAtNode
接口来卸载掉它和用户关闭掉浏览器tab页窗口,该顶层组件是不会有被销毁的时机的,它一直伴随着整个应用,因此咱们都会在该组件的componentDidMount
函数里发起一些请求来获取服务器端的配置型数据并缓存起来,方便整个应用全局使用。node
对于由路由系统挂载的页面组件,咱们一般也会在它的componentDidMount
函数里发起请求来获取该页面,若是状态是由store
管理的(如redux、或者mobx),若须要在页面组件的卸载的时候清理相应的store状态,则还会选择在componentWillUnmount
里调用相应的方法作清理。react
固然了,对于函数组件来讲使用useEffect
钩子函数作起来就一步到位,比起类组件显得更简单git
function PageComp(){ useEffect(()=>{ /** 等效于 componentDidMount 发起请求调用 */ return ()=>{ /** 等效于 componentWillUnmount 作相应的清理 */ } }, []) }
那本文题目提到的消灭生命周期又做何解释呢?看起来没有了它们咱们是没法完成相似需求的,在对此做出解释以前,咱们先列举一下如今的生命周期的使用体验问题。github
类组件和函数组件是没法作到0修改共用一套逻辑的,类组件在将来的很长一段时间内都将一直存在,这是咱们没法避免的问题,但类组件和函数组件的设计理念致使它们的生命周期函数使用方式是彻底不一样的,因此共享逻辑须要必定的改造web
已提高到store的状态的初始化流程却仍是和组件耦合在一块儿,这一点必定要注意一个前提,就是咱们一般在顶层组件的生命周期函数里完成store的某个节点的状态初始化,不论是根组件仍是页面组件,它们都具备顶层组件的性质,可是把store某节点的状态初始化流程写在组件里会带来一些额外的问题,redux
接下里让咱们看看在concent里是如何处理这些问题并消灭掉生命周期函数的呢。segmentfault
虽然类组件和函数的生命周期声明方式和使用方式彻底不同,可是咱们能够依靠组合api来抹掉这层差别,达到让类组件和函数组件都真正的只充当ui载体的目的api
假设有如下两个自管理状态的组件,他们都具备相同的功能,一个是类组件数组
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.mounted
和lifecyle.willUnmount
来完全解耦生命周期和组件的关系了,concent
内部会维护一个模块对应下的实例计数器,因此依靠这个功能能够精确控制模块状态的初始化时机了。
当前模块的第一个实例挂载完毕时触发,且仅触发一次,即当该模块的全部实例都销毁后,再次有一个实例挂载完毕,也不会触发了
run({ product: { lifecycle: { mounted: (dispatch)=> dispatch('initState') } } })
如需反复触发,即只要知足模块的实例数从0到1时就触发,返回false便可
当前模块的最后一个实例将销毁时触发,且仅触发一次,即当该模块再次生成了不少实例,而后又所有销毁,也不会触发了
run({ counter: { lifecycle: { willUnmount: dispatch=> dispatch('clearModuleState'), } } })
一样的如需反复触发,即只要知足模块的实例数从有变为0时就触发,返回false便可
若是该模块的状态和有无组件挂载无关系,则直接配置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 ,打造一站式云端内容管理系统,它是云开发推出的,基于 Node.js 的 Headless 内容管理平台,提供了丰富的内容管理功能,安装简单,易于二次开发,并与云开发的生态体系紧密结合,助力开发者提高开发效率。
concent
已为其管理后台提供强力支持,新版的管理界面更加美观和体贴了。
也欢迎小哥哥们来撩FFCreator,它是一个基于node.js的轻量、灵活的短视频加工库。您只须要添加几张图片或视频片断再加一段背景音乐,就能够快速生成一个很酷的视频短片。
FFCreator
是一种轻量又简单的解决方案,只须要不多的依赖和较低的机器配置就能够快速开始工做。而且它模拟实现了animate.css90%的动画效果,您能够轻松地把 web 页面端的动画效果转为视频,真的很给力。