redux和mobx入門使用

adouwt發表於2021-05-07

redux和mobx入門使用

專案涉及技術

公共外掛

  • create-react-app
  • react-dom
  • react-router
  • react-router-dom
  • react-hook
  • redux
  • react-redux

大概實現功能

  • 元件呼叫
  • 路由跳轉
  • 介面呼叫
  • 狀態管理

程式碼示例說明

redux 和 mobx 的層級結構如下

redux-mobx
├── README.md
├── mobx-demo
│ ├── README.md
│ ├── config-overrides.js
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── logo192.png
│ │ ├── logo512.png
│ │ ├── manifest.json
│ │ └── robots.txt
│ └── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── compments
│ │ ├── compnentA.js
│ │ └── compnentB.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── pages
│ │ ├── bar.js
│ │ └── foo.js
│ ├── reportWebVitals.js
│ ├── setupTests.js
│ └── store
│ ├── index.js
│ └── storeone.js
├── package-lock.json
└── redux-demo
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
└── src
├── App.css
├── App.js
├── App.test.js
├── compments
│ ├── compnentA.js
│ └── compnentB.js
├── index.css
├── index.js
├── logo.svg
├── pages
│ ├── bar.js
│ └── foo.js
├── reportWebVitals.js
├── setupTests.js
└── store
├── index.js
└── reducer.js

專案基礎腳手架使用 create-react-app 生成,新建三個資料夾,元件集components, 頁面集 pages, 以及狀態管理集 store。

1.App.js

    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import reportWebVitals from './reportWebVitals';
    import App from './App';
    // 新增程式碼如下
    import Foo from './pages/foo';
    import Bar from './pages/bar';
    import {HashRouter, Route, Switch } from 'react-router-dom'
    import {Provider} from 'react-redux';
    import store from './store/index'
    ReactDOM.render(
    <Provider store = {store}>
        <React.StrictMode>
            <h1>redux-demo</h1>
            <HashRouter>
                <Switch>
                    <Route exact path="/foo" component={Foo}/>
                    <Route exact path="/bar" component={Bar}/>
                    <Route exact path="/" component={App} />
                </Switch>
            </HashRouter>
        </React.StrictMode>
    </Provider>,
    document.getElementById('root')
    );
    reportWebVitals();

引入頁面Foo, Bar,路由 react-router-dom外掛,以及狀態管理 react-redux。狀態統一管理 store/index.js,這裡統一管理store。
通過Provider 傳入 store,包裹所有元件,這樣元件內部能拿到store資料。

注意
這裡的 Switch元件,當匹配到一個路由的時候,就不用往下匹配,這個也是專案優化的一個部分。

2.store 處理

store
├── index.js
└── reducer.js

index.js
我這裡演示的是一個reducer,如果是多個reducer 可以用 combineReducers 進行合併

    import { createStore } from 'redux';
    import reducer from './reducer';
    export default createStore(reducer)

定義簡單的action
reducer.js

    const defaultState = {
        count: 1,
        pageName: "default page"
    }

    const Store = (state = defaultState, action) => {
        switch(action.type) {
            case 'COUNT_ADD':
                state.count += action.value;
                break;
            case 'COUNT_SUB':
                state.count -= action.value;
                break;
            case 'COUNT_MUTIL':
                state.count *= action.value;
                break;
            default:
                state.count = defaultState.count;
        }

        return {
            ...state
        }
    }

    export default  Store

3.頁面路由 bar.js

    import React from 'react';
    import { connect } from 'react-redux';

    // 元件B中獲取count的值 
    function Bar(props) {
        const { count, pageName} = props
        return <div>
            <h2>Bar</h2>
            <p>count: {count}</p>
            <p>pageName: {pageName}</p>
        </div>
    }

    const BarPage = connect(state => (
        state
    ))(Bar)

    export default BarPage;

使用redux中 connect 將元件和store連結起來。connect 一共四個引數,可以看這個文章connect,這個元件裡 我們只用裡第一個引數,將store 中的值 傳遞給元件,這樣 Bar 函式元件props裡面就有我們傳進來的資料。

4.頁面路由 foo.js

    import React, {useState, useEffect} from 'react';
    import { connect } from 'react-redux'
    // 元件B中獲取count的值 
    // http://localhost:4000/get/alluser 介面地址
    const Foo =  (props) => {
        const { count } = props
        const [ allData, setAllData ] = useState([]);
        useEffect(()=>{
            fetch("http://localhost:4000/get/alluser").then(res => {
                res.json().then(data => {
                    setAllData(data.users)
                })
            })
        },[])
        
        return <div>
            <h2>Foo</h2>
            <p>count: {count}</p>
            <ul>
                {allData.map((item,index)=>{
                    return <li key={index}>
                        <p>{item.name}</p>
                        <img src={item.avatar_url} width="180"/>
                    </li>
                })}
            </ul>
        </div>
    }

    const FooPage = connect(state => (
        {
            count: state.count
        }
    ))(Foo)

    export default  FooPage

注意

  1. 這裡的connect 第一個引數,跟上面的例子差不多,返回的是state 裡面一個具體的值,這樣可以控制頁面元件裡的具體資料。
  2. HOOK 元件 使用useEffect 呼叫介面,獲取資料,渲染頁面。對於HOOK的使用 不是我們這節的重點,具體學習可以檢視react-hook

5.元件 commentA.js

注意,重點

    import React from 'react';
    import { connect } from 'react-redux'
    import { Link } from 'react-router-dom'

    const CompnentA = (props) => {
        // const [count, SetCount] = useState(props.count)
        const addCount = () => {
            const  { changeCount } = props;
            changeCount({
                type: "COUNT_ADD",
                value: 3,
            })
        }
        const subCount = () => {
            const  { changeCount } = props;
            changeCount({
                type: "COUNT_SUB",
                value: 1,
            })
        }
        const mutilCountfun = () => {
            const {mutilCount} = props;
            mutilCount({
                type: 'COUNT_MUTIL',
                value: 10
            })
        }
        return <div>
            <h2>元件A</h2>
            <p><button onClick={addCount}>count +3</button></p>
            <p><button onClick={subCount}>count -1</button></p>
            <p><button onClick={mutilCountfun}>count * 10</button></p>
            <ul>
                <li><Link to="/foo">Foo 頁面</Link></li>
                <li><Link to="/bar">Bar 頁面</Link></li>
                <li><Link to="/">APP 主頁面</Link></li>
            </ul>
        </div>
    }

    const Compnent = connect(null, dispatch => ({
        changeCount: ({type, value}) => dispatch({
            type,
            value, 
        }),
        mutilCount: ({type, value}) => dispatch({
            type,
            value
        })
    }))(CompnentA)

    export default Compnent

connect 第一個引數,因為不需要,我們沒有傳store進元件,第二個函式引數mapDispatchToProps,我們傳遞了一些action方法進去,這些方法會繫結到元件當中。
我們知道我們不能直接修改store 裡面的資料,我們需要通過派發器(dispatch)派發一個動作(action),這也是唯一修改state的方法,
這個action方法會觸發我們的reducer方法 ,根據對應的action,返回對應的state。

6.元件 comnentB.js

通過上面 componentA的操作,我們已經修改了state 值,在componentB 中就體現出來了,程式碼如下

    import React  from 'react';
    import { connect } from 'react-redux'

    // 元件B中獲取count的值 
    function CompnentB(props) {
        const { count } = props
        return <div>
            <h2>元件B</h2>
            <p>count: {count}</p>
        </div>
    }

    const CompnentBB = connect(state => (
        {
            count: state.count
        }
    ))(CompnentB)

    export default CompnentBB

整體效果,圖片如下
redux-demo

以上是redux 部分的demo,我們繼續使用mobx,整個專案複製一遍,我們修改下資料管理部分和頁面使用狀態資料部分,其他層級不變,開始的專案結構可以看出。

mobx-demo

1. index.js

    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import reportWebVitals from './reportWebVitals';

    import App from './App';
    import Foo from './pages/foo';
    import Bar from './pages/bar';
    import { configure } from "mobx";
    import { Provider } from "mobx-react";
    import store from './store/index'
    import {HashRouter, Route, Switch } from 'react-router-dom'
    configure({ enforceActions: "observed" });
    ReactDOM.render(
    <Provider store = {store}>
        <React.StrictMode>
        <h1>mobx-demo</h1>
        <HashRouter>
            <Switch>
                <Route exact path="/foo" component={Foo}/>
                <Route exact path="/bar" component={Bar}/>
                <Route exact path="/" component={App} />
            </Switch>
        </HashRouter>
        </React.StrictMode>
    </Provider>,
    document.getElementById('root')
    );

    reportWebVitals();

這裡主要是將redux 的Provider 替換成 mobx的Provider

2. store/index

store
├── index.js
└── storeone.js

  1. index.js

        import StoreOne from "./storeone";
        class Store {
            constructor() {
            this.storeOne = StoreOne;
            }
        }
        export default new Store();
    
  2. storeone.js

        import { observable, action, makeObservable } from 'mobx';
        class appStore {
            constructor() {
                // 新增makeObservable mobx6.0 狀態資料已經修改,但是頁面沒有更新,需要通過這個方法來強制更新資料
                makeObservable(this)
            }
            // state
            @observable count = 1;
            @observable pageName = "default pageName";
    
            // getter
            get skinWindow() {
                return {
                };
            }
    
            // action
            @action
            addCount(payload) {
                this.count += payload;
            }
            @action
            subCount(payload) {
                this.count -= payload;
            }
    
            @action
            mutilCountfun(payload) {
                this.count *= payload;
            }
        }
        const as = new appStore();
        export default as
    

3.componentA 元件使用

    import React from 'react';
    import { Link } from 'react-router-dom'
    import { observer, inject } from 'mobx-react';

    const CompnentA = (props) => {
        const {storeOne} =  props.store
        const addCount = () => {
            storeOne.addCount(3)
        }
        const subCount = () => {
            storeOne.subCount(1)
        }
        const mutilCountfun = () => {
            storeOne.mutilCountfun(10)
        }
        return <div>
            <h2>元件A</h2>
            <p><button onClick={addCount}>count +3</button></p>
            <p><button onClick={subCount}>count -1</button></p>
            <p><button onClick={mutilCountfun}>count * 10</button></p>
            <ul>
                <li><Link to="/foo">Foo 頁面</Link></li>
                <li><Link to="/bar">Bar 頁面</Link></li>
                <li><Link to="/">APP 主頁面</Link></li>
            </ul>
        </div>
    }

    export default inject('store')(observer(CompnentA));

inject('store')(observer(CompnentA)) => 可以理解為將store 注入 可監測的 CompnentA 中。

點選事件方法 addCount 它會觸發 store 裡面的 action 方法,

    addCount(payload) {
        this.count += payload;
    }

action 方法 修改 state上的資料,頁面上的資料會及時重新整理。

從這裡我們就可以明顯看出與redux的不同,不需要通過reducer 去根據不同的type 來修改state 上的資料,而是直接通過action方法直接修改。

4.其他頁面使用mobx的姿勢

export default inject('store')(observer(Bar)); => 可以理解為將store 注入 可監測的 頁面 Bar 中。

    import React  from 'react';
    import { inject, observer } from 'mobx-react';

    // 元件B中獲取count的值 
    function Bar(props) {
        const { count, pageName} = props.store.storeOne
        return <div>
            <h2>Bar</h2>
            <p>count: {count}</p>
            <p>pageName: {pageName}</p>
        </div>
    }

    export default inject('store')(observer(Bar));

5. create-react-app 生成的腳手架不支援@ 裝飾器語法的方案。

  1. package.json 修改啟動命令
"scripts": {
    "start": "PORT=8000 react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-app-rewired eject"
  },
  1. 安裝外掛,
npm install customize-cra react-app-rewired @babel/plugin-proposal-decorators --save

  1. 在src下 新建檔案 config-overrides.js
    const{override,addDecoratorsLegacy}=require('customize-cra');
    module.exports=override(addDecoratorsLegacy());

整體效果圖片,如下
mobx-demo

上面的演示程式碼已上傳 [github] (https://github.com/adouwt/redux-mobx), 如有錯誤,敬請指出,如需轉載清說明出處。感謝閱讀?

相關文章