如何優雅的消滅掉react生命週期函數

開源不易,感謝你的支持,❤ star concent^_^css

elegant.png

序言

在react應用裏,存在一個頂層組件,該組件的生命週期很長,除了人爲的調用unmountComponentAtNode接口來卸載掉它和用戶關閉掉瀏覽器tab頁窗口,該頂層組件是不會有被銷燬的時機的,它一直伴隨着整個應用,因此咱們都會在該組件的componentDidMount函數裏發起一些請求來獲取服務器端的配置型數據並緩存起來,方便整個應用全局使用。node

對於由路由系統掛載的頁面組件,咱們一般也會在它的componentDidMount函數裏發起請求來獲取該頁面,若是狀態是由store管理的(如redux、或者mobx),若須要在頁面組件的卸載的時候清理相應的store狀態,則還會選擇在componentWillUnmount裏調用相應的方法作清理。react

image.png

固然了,對於函數組件來講使用useEffect鉤子函數作起來就一步到位,比起類組件顯得更簡單git

function PageComp(){
  useEffect(()=>{
    /** 等效於 componentDidMount 發起請求調用 */
    return ()=>{
      /** 等效於 componentWillUnmount 作相應的清理 */
    }
  }, [])
}

當前生命週期函數的使用體驗

那本文題目提到的消滅生命週期又做何解釋呢?看起來沒有了它們咱們是沒法完成相似需求的,在對此做出解釋以前,咱們先列舉一下如今的生命週期的使用體驗問題。github

沒法共用一套邏輯

類組件和函數組件是沒法作到0修改共用一套邏輯的,類組件在將來的很長一段時間內都將一直存在,這是咱們沒法避免的問題,但類組件和函數組件的設計理念致使它們的生命週期函數使用方式是徹底不一樣的,因此共享邏輯須要必定的改造web

初始化流程和組件耦合在一塊兒

已提高到store的狀態的初始化流程卻仍是和組件耦合在一塊兒,這一點必定要注意一個前提,就是咱們一般在頂層組件的生命週期函數裏完成store的某個節點的狀態初始化,不論是根組件仍是頁面組件,它們都具備頂層組件的性質,可是把store某節點的狀態初始化流程寫在組件裏會帶來一些額外的問題,redux

  • 若是另外一個頁面組件也須要使用該節點數據時,須要額外的檢查狀態有沒有初始化好
  • 當重構頂層組件的時候要當心翼翼的維護好這些聲明週期邏輯

接下里讓咱們看看在concent裏是如何處理這些問題並消滅掉生命週期函數的呢。segmentfault

使用組合api統一邏輯

雖然類組件和函數的生命週期聲明方式和使用方式徹底不同,可是咱們能夠依靠組合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消除生命週期

當咱們的頁面組件狀態提高到模塊裏時,咱們可使用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

歡迎小哥哥們來撩CloudBase CMS ,打造一站式雲端內容管理系統,它是雲開發推出的,基於 Node.js 的 Headless 內容管理平臺,提供了豐富的內容管理功能,安裝簡單,易於二次開發,並與雲開發的生態體系緊密結合,助力開發者提高開發效率。

concent已爲其管理後臺提供強力支持,新版的管理界面更加美觀和體貼了。

FFCreator

也歡迎小哥哥們來撩FFCreator,它是一個基於node.js的輕量、靈活的短視頻加工庫。您只須要添加幾張圖片或視頻片斷再加一段背景音樂,就能夠快速生成一個很酷的視頻短片。

FFCreator是一種輕量又簡單的解決方案,只須要不多的依賴和較低的機器配置就能夠快速開始工做。而且它模擬實現了animate.css90%的動畫效果,您能夠輕鬆地把 web 頁面端的動畫效果轉爲視頻,真的很給力。
相關文章
相關標籤/搜索