Umi 小白紀實(四)—— 在 Umi 3.x 中使用 dva


公司的新項目用到了 Umi,之前用過 Umi 1.x 版本,而現在已經來到了 3.x 時代

相對低版本來說,Umi 3.x 的整體的設計沒有什么大變化,但在細節上還是有着不小的改變

比如 model,除了兼容以前的 connect 寫法之外,還可以使用 hooks,這篇文章主要是對 hooks 寫法的介紹

// Umi 3.x 默認使用 ts,但為了便於理解,文中的代碼都是用的 js,可以自行調整為 ts

 

一、model 用例

model 的結構和之前沒有變化,依然是包含以下幾部分

export default { namespace: '',     // 表示在全局 state 上的 key
  state: {},         // 狀態數據
  reducers: {},      // 管理同步方法,必須是純函數
  effects: {},       // 管理異步操作,采用了 generator 的相關概念
  subscriptions: {}, // 訂閱數據源
};

詳細介紹可以參考之前的文章《Umi 小白紀實(二)—— model 的注冊與使用》



這里先寫一個簡單的 testModel,后面會基於此來介紹 dva 的 hooks 

const testModel = { namespace: 'testModel', state: { count: 0, }, reducers: { change: state => { state.count++; return state; }, }, } export default testModel;

 

 

二、老辦法 connect

如果是用 connect 寫法,需要用 dva 或 umi 中導出 connect 方法,然后將 model 綁定到組件

// test-page
 import { connect } from 'umi'; import styles from './index.module.less'; function TestPage(props) { // title 在 testModel 中定義, dispatch 由 dva 提供
  const { count, dispatch } = props; const handleClick = () => { // 使用 dispatch 調用 testModel 中的 change 方法
    dispatch({ type: "testModel/change" }) } return ( <div className={styles.testPage}>
      <h1>Hello World</h1>
      <h3>{count}</h3>
      <button onClick={handleClick}>Update Count</button>
    </div> ); } // 這里的 testModel 是對應 model 的 namespace
function mapStateToProps({ testModel }) { return { ...testModel }; }; // 使用 connect 將數據關聯到組件
export default connect(mapStateToProps)(TestPage);

這種方式在 Umi 3.x 中依然可以使用,不過既然用上了最新的 React 和 Umi,為什么不嘗試一下 hooks 呢?

 

 

三、新思路 hooks

dva 2.6x 之后,就提供了 useSelector、useDispatch 這兩個 hook

如果用過 redux,對它們應該不算陌生,而事實上 dva 確實是基於 redux 實現,所以這些 api 的用法和 redux 中沒什么區別

 

這種思路不會引用 connect,也就不會將 model 中的數據直接關聯到組件中,但可以通過 useSelector 拿到 model 中的 state

而且通過 useSelector 獲取的 state 會建立發布訂閱模式,當 model 中的 state 變化的時候,會再次執行 useSelector 以更新組件中的 state

// before
connect(Model, Component); const { count, dispatch } = props; // after
const dispatch = useDispatch(); const count = useSelector(state => state.testModel.count);

 

所以上面使用 connect 的組件,在使用 hooks 之后可以這么來:

// test-page
 import { useSelector, useDispatch } from 'umi'; import styles from './index.module.less'; function TestPage(props) { // 通過 hook 獲取 dispatch 和 count
  const dispatch = useDispatch(); const count = useSelector(state => state.testModel.count); const handleClick = () => { // 使用 dispatch 調用 testModel 中的 change 方法
    dispatch({ type: "testModel/change" }) } return ( <div className={styles.testPage}>
      <h1>Hello World</h1>
      <h3>{count}</h3>
      <button onClick={handleClick}>Update Count</button>
    </div> ); } export default TestPage;

 

 

四、新問題:路由切換后 state 沒有重置

先來跑一下測試頁面吧:

可以看到,在跳轉回首頁之后,測試頁面 model 的 state 並沒有重置

這個問題本身倒不難解決,在組件銷毀的時候重置 state 就可以

// model.js

const getDefaultState = () => ({ title: 'there is test page', }) const testModel = { namespace: 'testModel', state: getDefaultState(), reducers: { // 創建重置 state 的方法
 reset: getDefaultState, }, } export default testModel;

但在純函數組件中,怎么判斷銷毀呢?

關於這一點可以看一下 useEffect 的官方文檔:如果你的 effect 返回一個函數,React 將會在執行清除操作時調用它

但僅僅如此還不夠,因為這個清除操作並不是銷毀組件,我們需要使用 useEffect 的第二個參數

import { useEffect } from 'react'; function TestPage(props) { // ... useEffect(() => { // 調用 model 中的 reset 方法重置
        return dispatch({ type: "testModel/reset" }); // 傳入空數組,返回的函數會在組件銷毀時執行
 }, []); // ...
} export default TestPage;

給 useEffect 傳入一個空數組,就能讓它只在渲染完成后加載,並在組件銷毀時執行其返回的函數,類似於 componentDidMountcomponentWillUnmount

當然這里也可以通過路由地址來判斷:

import { useLocation } from 'umi'; function TestPage(props) { // ...
    const location = useLocation(); useEffect(() => { // ...
 }, [location.pathname]); // ...
}

這樣該 hook 就只會在路由地址發生變化時執行

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM