Redux存储与React状态

如何在React应用程序中设计数据存储?在哪里存储应用程序数据:在全局存储(Redux存储)中还是在本地存储(组件状态)中?

这些问题来自于开始使用Redux库的开发人员,甚至是积极使用它的开发人员。



在React的5年发展中,BENOVATE的我们已经在实践中测试了各种构建此类应用程序架构的方法。在本文中,我们将考虑在应用程序中选择在何处存储数据的可能标准。



也许根本没有Redux?是的,如果您可以不这样做的话。关于这个话题,你可以阅读的文章从图书馆的创建者之一-丹·阿布拉莫夫。如果开发人员了解Redux是必不可少的,那么选择数据仓库有以下几个标准:



  1. 数据寿命
  2. 使用频率
  3. 跟踪状态变化的能力


数据寿命



有2个类别:



  • 经常更改数据。
  • 很少更改数据。在用户直接使用应用程序期间或与应用程序之间的会话之间,此类数据很少更改。


经常更改数据



该类别包括,例如,实现与对象列表配合工作的组件的过滤,排序和逐页导航参数,或负责在应用程序中显示单个UI元素的标志,例如下拉列表或模式窗口(前提是未锚定)用户设置)。这也可以包括填写表格的数据,直到将其发送到服务器。



最好在组件状态下存储此类数据,因为 它们使全局存储混乱不堪,并使它们的工作复杂化:您需要编写操作,缩减器,初始化状态并及时清除它。



不好的例子
import React from 'react';
import { connect } from 'react-redux';
import { toggleModal } from './actions/simpleAction'
import logo from './logo.svg';
import './App.css';
import Modal from './elements/modal';

const  App = ({
                  openModal,
                  toggleModal,
              }) => {
    return (
        <div className="App">
            <header className="App-header">
                <img src={logo} className="App-logo" alt="logo" />
            </header>
            <main className="Main">
                <button onClick={() => toggleModal(true)}>{'Open  Modal'}</button>
            </main>
            <Modal isOpen={openModal} onClose={() => toggleModal(false)} />
        </div>
    );
}

const mapStateToProps = (state) => {
    return {
        openModal: state.simple.openModal,
    }
}

const mapDispatchToProps = { toggleModal }

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(App)

// src/constants/simpleConstants.js
export const simpleConstants = {
    TOGGLE_MODAL: 'SIMPLE_TOGGLE_MODAL',
};

// src/actions/simpleAction.js
import { simpleConstants} from "../constants/simpleConstants";

export const toggleModal = (open) => (
    {
        type: simpleConstants.TOGGLE_MODAL,
        payload: open,
    }
);

// src/reducers/simple/simpleReducer.js
import { simpleConstants } from "../../constants/simpleConstants";

const initialState = {
    openModal: false,
};

export function simpleReducer(state = initialState, action) {
    switch (action.type) {
        case simpleConstants.TOGGLE_MODAL:
            return {
                ...state,
                openModal: action.payload,
            };
        default:
            return state;
    }
}




好的例子
import React, {useState} from 'react';
import logo from './logo.svg';
import './App.css';
import Modal from './elements/modal';

const  App = () => {
  const [openModal, setOpenModal] = useState(false);
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
      </header>
      <main className="Main">
          <button onClick={() => setOpenModal(true)}>{'Open  Modal'}</button>
      </main>
      <Modal isOpen={openModal} onClose={() => setOpenModal(false)} />
    </div>
  );
}

export default App;




很少更改数据



这些数据通常在页面更新之间或用户对页面的单独访问之间不会更改。



由于刷新页面时会重新创建Redux存储库,因此此类数据应存储在其他位置:服务器上的数据库或浏览器中的本地存储库。



它可以是目录或用户设置的数据。例如,在开发使用自定义设置的应用程序时,在用户身份验证之后,我们会将这些设置保存在Redux存储中,这使应用程序组件无需访问服务器即可使用它们。



值得记住的是,在没有用户干预的情况下,某些数据可能会在服务器上更改,因此您必须考虑应用程序将如何响应。



不好的例子
// App.js
import React from 'react';
import './App.css';
import Header from './elements/header';
import ProfileEditForm from './elements/profileeditform';

const  App = () => {
  return (
    <div className="App">
      <Header />
      <main className="Main">
          <ProfileEditForm />
      </main>
    </div>
  );
}

export default App;

// src/elements/header.js
import React from "react";
import logo from "../logo.svg";
import Menu from "./menu";

export default () => (
    <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <Menu />
    </header>
)

// src/elements/menu.js
import React, {useEffect, useState} from "react";
import { getUserInfo } from '../api';

const Menu = () => {

    const [userInfo, setUserInfo] = useState({});

    useEffect(() => {
        getUserInfo().then(data => {
            setUserInfo(data);
        });
    }, []);

    return (
        <>
            <span>{userInfo.userName}</span>
            <nav>
                <ul>
                    <li>Item 1</li>
                    <li>Item 2</li>
                    <li>Item 3</li>
                    <li>Item 4</li>
                </ul>
            </nav>
        </>
    )
}

export default Menu;

// src/elements/profileeditform.js
import React, {useEffect, useState} from "react";
import {getUserInfo} from "../api";

const ProfileEditForm = () => {

    const [state, setState] = useState({
        isLoading: true,
        userName: null,
    })

    const setName = (e) => {
        const userName = e.target.value;
        setState(state => ({
            ...state,
            userName,
        }));
    }
    useEffect(() => {
        getUserInfo().then(data => {
            setState(state => ({
                ...state,
                isLoading: false,
                userName: data.userName,
            }));
        });
    }, []);

    if (state.isLoading) {
        return null;
    }

    return (
        <form>
            <input type="text" value={state.userName} onChange={setName} />
            <button>{'Save'}</button>
        </form>
    )
}

export default ProfileEditForm;




好的例子
// App.js
import React, {useEffect} from 'react';
import {connect} from "react-redux";
import './App.css';
import Header from './elements/header';
import ProfileEditForm from './elements/profileeditform';
import {loadUserInfo} from "./actions/userAction";

const  App = ({ loadUserInfo }) => {

  useEffect(() => {
      loadUserInfo()
  }, [])

  return (
    <div className="App">
      <Header />
      <main className="Main">
          <ProfileEditForm />
      </main>
    </div>
  );
}

export default connect(
    null,
    { loadUserInfo },
)(App);

// src/elements/header.js
import React from "react";
import logo from "../logo.svg";
import Menu from "./menu";

export default () => (
    <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <Menu />
    </header>
)

// src/elements/menu.js
import React from "react";
import { connect } from "react-redux";

const Menu = ({userName}) => (
    <>
        <span>{userName}</span>
        <nav>
            <ul>
                <li>Item 1</li>
                <li>Item 2</li>
                <li>Item 3</li>
                <li>Item 4</li>
            </ul>
        </nav>
    </>
)

const mapStateToProps = (state) => {
    return {
        userName: state.userInfo.userName,
    }
}

export default connect(
    mapStateToProps,
)(Menu);

// src/elements/profileeditform.js
import React from "react";
import { changeUserName } from '../actions/userAction'
import {connect} from "react-redux";

const ProfileEditForm = ({userName, changeUserName}) => {

    const handleChange = (e) => {
        changeUserName(e.target.value);
    };

    return (
        <form>
            <input type="text" value={userName} onChange={handleChange} />
            <button>{'Save'}</button>
        </form>
    )
}

const mapStateToProps = (state) => {
    return {
        userName: state.userInfo.userName,
    }
}

const mapDispatchToProps = { changeUserName }

export default connect(
    mapStateToProps,
    mapDispatchToProps,
)(ProfileEditForm);

// src/constants/userConstants.js
export const userConstants = {
    SET_USER_INFO: 'USER_SET_USER_INFO',
    SET_USER_NAME: 'USER_SET_USER_NAME',
    UNDO: 'USER_UNDO',
    REDO: 'USER_REDO',
};

// src/actions/userAction.js
import { userConstants } from "../constants/userConstants";
import { getUserInfo } from "../api/index";

export const changeUserName = (userName) => (
    {
        type: userConstants.SET_USER_NAME,
        payload: userName,
    }
);

export const setUserInfo = (data) => (
    {
        type: userConstants.SET_USER_INFO,
        payload: data,
    }
)

export const loadUserInfo = () => async (dispatch) => {
    const result = await getUserInfo();
    dispatch(setUserInfo(result));
}

// src/reducers/user/userReducer.js
import { userConstants } from "../../constants/userConstants";

const initialState = {
    userName: null,
};

export function userReducer(state = initialState, action) {
    switch (action.type) {
        case userConstants.SET_USER_INFO:
            return {
                ...state,
                ...action.payload,
            };
        case userConstants.SET_USER_NAME:
            return {
                ...state,
                userName: action.payload,
            };
        default:
            return state;
    }
}




使用频率



第二个标准是React应用程序中应有多少个组件可以访问同一状态。在状态中使用相同数据的组件越多,使用Redux存储的好处就越大。



如果您了解某个特定组件或应用程序的一小部分状态是隔离的,那么最好使用单独组件或HOC组件的React状态。



状态深度



在没有Redux的应用程序中,React状态数据应存储在最顶层(在树中)组件中,假设我们避免将相同的数据存储在不同的位置,则其子组件将需要访问此数据。



有时,处于不同嵌套级别的大量子组件需要来自父组件状态的数据,这导致组件之间的强大互锁以及其中无用代码的出现,每当您发现子组件需要访问新的状态数据时,进行编辑的代价就很高。在这种情况下,明智的做法是将状态保存在Redux中,并从相应组件中的存储中检索必要的数据。



如果有必要在一两个嵌套级别将状态数据传输到子组件,则可以在不使用Redux的情况下执行此操作。



不好的例子
//App.js

import React from 'react';
import './App.css';
import Header from './elements/header';
import MainContent from './elements/maincontent';

const  App = ({userName}) => {
  return (
    <div className="App">
      <Header userName={userName} />
      <main className="Main">
          <MainContent />
      </main>
    </div>
  );
}

export default App;

// ./elements/header.js

import React from "react";
import logo from "../logo.svg";
import Menu from "./menu";

export default ({ userName }) => (
    <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <Menu userName={userName} />
    </header>
)

// ./elements/menu.js
import React from "react";

export default ({userName}) => (
    <>
        <span>{userName}</span>
        <nav>
            <ul>
                <li>Item 1</li>
                <li>Item 2</li>
                <li>Item 3</li>
                <li>Item 4</li>
            </ul>
        </nav>
    </>
)




好的例子
// App.js
import React from 'react';
import './App.css';
import Header from './elements/header';
import MainContent from './elements/maincontent';

const  App = () => {
  return (
    <div className="App">
      <Header />
      <main className="Main">
          <MainContent />
      </main>
    </div>
  );
}

export default App;

//./elements/header.js

import React from "react";
import logo from "../logo.svg";
import Menu from "./menu";

export default () => (
    <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <Menu />
    </header>
)

//./elements/menu.js
import React from "react";
import { connect } from "react-redux";

const Menu = ({userName}) => (
    <>
        <span>{userName}</span>
        <nav>
            <ul>
                <li>Item 1</li>
                <li>Item 2</li>
                <li>Item 3</li>
                <li>Item 4</li>
            </ul>
        </nav>
    </>
)

const mapStateToProps = (state) => {
    return {
        userName: state.userInfo.userName,
    }
}

export default connect(
    mapStateToProps,
)(Menu)




在状态下对相同数据进行操作的未绑定组件



在某些情况下,几个相对不相关的组件需要访问同一状态。例如,应用程序需要创建一个用于编辑用户配置文件和标题的表单,该表单还需要显示用户数据。



当您创建存储用户配置文件数据的顶级超级组件时,当然有可能变得极端,首先将其传递给头组件及其子组件,然后将其传递到树的更深处。到配置文件编辑组件。在这种情况下,您还需要将回调传递到配置文件编辑表单,该表单将在用户数据更改时被调用。



首先,这种方法可能导致组件之间的紧密链接,中间组件中不必要数据的出现和不必要代码的出现,这将需要时间进行更新和维护。



其次,无需进行其他代码更改,很可能您将获得不使用传递给他们的数据的组件,而是在每次更新该数据时将其呈现,这将导致应用程序速度降低。



为了简化操作,我们将用户的配置文件数据保存在Redux存储中,并让标头容器组件和配置文件编辑组件在Redux存储中接收和修改数据。



图片



不好的例子
// App.js
import React, {useState} from 'react';
import './App.css';
import Header from './elements/header';
import ProfileEditForm from './elements/profileeditform';

const  App = ({user}) => {
  const [userName, setUserName] = useState(user.user_name);
  return (
    <div className="App">
      <Header userName={userName} />
      <main className="Main">
          <ProfileEditForm onChangeName={setUserName} userName={userName} />
      </main>
    </div>
  );
}

export default App;

// ./elements/header.js
import React from "react";
import logo from "../logo.svg";
import Menu from "./menu";

export default ({ userName }) => (
    <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <Menu userName={userName} />
    </header>
)

// ./elements/menu.js
import React from "react";

const Menu = ({userName}) => (
    <>
        <span>{userName}</span>
        <nav>
            <ul>
                <li>Item 1</li>
                <li>Item 2</li>
                <li>Item 3</li>
                <li>Item 4</li>
            </ul>
        </nav>
    </>
)

export default Menu;

// ./elements/profileeditform.js
import React from "react";

export default ({userName, onChangeName}) => {

    const handleChange = (e) => {
        onChangeName(e.target.value);
    };

    return (
        <form>
            <input type="text" value={userName} onChange={handleChange} />
            <button>{'Save'}</button>
        </form>
    )
}




好的例子
// App.js
import React from 'react';
import './App.css';
import Header from './elements/header';
import ProfileEditForm from './elements/profileeditform';

const  App = () => {
  return (
    <div className="App">
      <Header />
      <main className="Main">
          <ProfileEditForm />
      </main>
    </div>
  );
}

export default App;

//./elements/header.js
import React from "react";
import logo from "../logo.svg";
import Menu from "./menu";

export default () => (
    <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <Menu />
    </header>
)

//./elements/menu.js

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

const Menu = ({userName}) => (
    <>
        <span>{userName}</span>
        <nav>
            <ul>
                <li>Item 1</li>
                <li>Item 2</li>
                <li>Item 3</li>
                <li>Item 4</li>
            </ul>
        </nav>
    </>
)

const mapStateToProps = (state) => {
    return {
        userName: state.userInfo.userName,
    }
}

export default connect(
    mapStateToProps,
)(Menu)

//./elements/profileeditform

import React from "react";
import { changeUserName } from '../actions/userAction'
import {connect} from "react-redux";

const ProfileEditForm = ({userName, changeUserName}) => {

    const handleChange = (e) => {
        changeUserName(e.target.value);
    };

    return (
        <form>
            <input type="text" value={userName} onChange={handleChange} />
            <button>{'Save'}</button>
        </form>
    )
}

const mapStateToProps = (state) => {
    return {
        userName: state.userInfo.userName,
    }
}

const mapDispatchToProps = { changeUserName }

export default connect(
    mapStateToProps,
    mapDispatchToProps,
)(ProfileEditForm)




跟踪状态变化的能力



另一种情况:您需要实现在应用程序中撤消/重做用户操作的功能,或者只想记录状态更改。



在开发教程设计器时,我们有这样的需求,用户可以使用它在教程页面上添加和自定义带有文本,图像和视频的块,还可以执行撤消/重做操作。



在这些情况下,Redux是一个很好的解决方案,因为 创建的每个动作都是对状态的原子改变。Redux通过将它们集中在一个地方-Redux store中,简化了所有这些任务。



撤消/重做示例
// App.js
import React from 'react';
import './App.css';
import Header from './elements/header';
import ProfileEditForm from './elements/profileeditform';

const  App = () => {
  return (
    <div className="App">
      <Header />
      <main className="Main">
          <ProfileEditForm />
      </main>
    </div>
  );
}

export default App;

// './elements/profileeditform.js'
import React from "react";
import { changeUserName, undo, redo } from '../actions/userAction'
import {connect} from "react-redux";

const ProfileEditForm = ({ userName, changeUserName, undo, redo, hasPast, hasFuture }) => {

    const handleChange = (e) => {
        changeUserName(e.target.value);
    };

    return (
        <>
            <form>
                <input type="text" value={userName} onChange={handleChange} />
                <button>{'Save'}</button>
            </form>
            <div>
                <button onClick={undo} disabled={!hasPast}>{'Undo'}</button>
                <button onClick={redo} disabled={!hasFuture}>{'Redo'}</button>
            </div>
        </>
    )
}

const mapStateToProps = (state) => {
    return {
        hasPast: !!state.userInfo.past.length,
        hasFuture: !!state.userInfo.future.length,
        userName: state.userInfo.present.userName,
    }
}

const mapDispatchToProps = { changeUserName, undo, redo }

export default connect(
    mapStateToProps,
    mapDispatchToProps,
)(ProfileEditForm)

// src/constants/userConstants.js
export const userConstants = {
    SET_USER_NAME: 'USER_SET_USER_NAME',
    UNDO: 'USER_UNDO',
    REDO: 'USER_REDO',
};

// src/actions/userAction.js
import { userConstants } from "../constants/userConstants";

export const changeUserName = (userName) => (
    {
        type: userConstants.SET_USER_NAME,
        payload: userName,
    }
);

export const undo = () => (
    {
        type: userConstants.UNDO,
    }
);

export const redo = () => (
    {
        type: userConstants.REDO,
    }
);

// src/reducers/user/undoableUserReducer.js
import {userConstants} from "../../constants/userConstants";
export function undoable(reducer) {
    const initialState = {
        past: [],
        present: reducer(undefined, {}),
        future: [],
    };

    return function userReducer(state = initialState, action) {
        const {past, present, future} = state;
        switch (action.type) {
            case userConstants.UNDO:
                const previous = past[past.length - 1]
                const newPast = past.slice(0, past.length - 1)
                return {
                    past: newPast,
                    present: previous,
                    future: [present, ...future]
                }
            case userConstants.REDO:
                const next = future[0]
                const newFuture = future.slice(1)
                return {
                    past: [...past, present],
                    present: next,
                    future: newFuture
                }
            default:
                const newPresent = reducer(present, action)
                if (present === newPresent) {
                    return state
                }
                return {
                    past: [...past, present],
                    present: newPresent,
                    future: []
                }
        }
    }
}

// src/reducers/user/userReducer.js
import { undoable } from "./undoableUserReducer";
import { userConstants } from "../../constants/userConstants";

const initialState = {
    userName: 'username',
};

function reducer(state = initialState, action) {
    switch (action.type) {
        case userConstants.SET_USER_NAME:
            return {
                ...state,
                userName: action.payload,
            };
        default:
            return state;
    }
}

export const userReducer = undoable(reducer);




总结一下



在以下情况下,请考虑将数据存储在Redux存储中:



  1. 如果此数据很少更改;
  2. 如果在几个(超过2-3个)相关组件或不相关组件中使用了相同的数据;
  3. 如果要跟踪数据更改。


在所有其他情况下,最好使用React状态。



PS非常感谢mamdaxx111 为准备本文提供帮助!



All Articles