在 React 中使用 Redux 的四种写法

更新日期: 2022-06-10阅读: 915标签: redux

大家好,我是前端西瓜哥。今天我们看看react 中使用 Redux 的 4 种写法。

Redux 是一种状态容器 JS 库,提供可预测的状态管理,经常和 React 配合来管理应用的全局状态,进行响应式组件更新。

Redux 一般来说并不是必须的,只有在项目比较复杂的时候,比如多个分散在不同地方的组件使用同一个状态。对于这种情况,如果通过 props 层层传递,代码会变得不可维护,这时候我们可以考虑使用 Redux 这类状态管理库。

不使用 Redux 的写法

我们创建一个 User 组件,显示用户名,并支持设置用户名。先看看不使用 Redux 的写法。

import { Component, createRef } from 'react';
class User extends Component {
  state = { username: '前端西瓜哥' };
  inputRef = createRef();
  setUsername = () => {
    this.setState({ username: this.inputRef.current.value });
  };
  render() {
    return (
      <div>
        <div>用户名: {this.state.username}</div>
        <input ref={this.inputRef} type="text" />
        <button onClick={this.setUsername}>设置用户名</button>
      </div>
    );
  }
}
export default User;

下面我们改造一下这个组件,将状态迁移到 Redux 里。

最底层的写法

Redux 是和框架无关的,我们先看看只用 Redux 库的写法。

demo:https://codesandbox.io/s/redux-plain-demo-bc4kv0。

首先我们创建一个 reducer。

// user_reducer.js
import { SET_USERNAME } from './constants';
// 初始值
const defaultState = {
  name: '前端西瓜哥',
  age: 88
};
// 用于修改 user 状态的 reducer
export const userReducer = (preState = defaultState, action) => {
  switch (action.type) {
    case SET_USERNAME: // type 值都统一放到 constants
      return { ...preState, name: action.payload };
    // 这里还可以根据需要,添加类似 setUserAga 等逻辑
    default:
      return preState;
  }
};
// constants.js
export const SET_USERNAME = 'SET_USERNAME';

reducer 是一个用于更新状态的函数,接收原来的状态 preState 和一个更新动作对象 action。

action 对象有一个  表示此次操作的描述 type 和  其他数据属性(通常为 payload) 。payload 会以某种方式去计算出一个新的状态,替换掉 redux 中原来的 state。

{
 type: 'SET_USERNAME',
 payload: '新用户名'
}

type 通常是一个字符串,比如我们会用  'COUNT_INCREMENT' 来给一个计数器加一,或用  'SET_USERNAME' 来更新用户名。reducer 会根据不同的 type 来执行不同的更新 state 行为。

action 的构造我们通常会用一个函数帮忙构建,这种函数称为 Action Creator:

// user_action.js
import { SET_USERNAME } from './constants';
export const setUsernameAction = (data) => {
  return {
    type: SET_USERNAME,
    payload: data
  };
};

有了 reducer,我们可以用它们来构建我们的 store。store 可以访问所有的保存在 redux 状态:

import { combineReducers, createStore } from 'redux';
import { userReducer } from './user_reducer';
const store = createStore(
  combineReducers({
    user: userReducer
  })
);
export default store;

combineReducers 可以将多个 reducer 组合在一起,有各自对应的属性名。比如上面的代码,我们可以通过  store.getState().user 来拿到用户对象。

如果你又新增了 counter 状态对象,只需再加上  counter: counterReducer ,就可以用  store.getState().counter 来拿到这个对象。

createStore 用于创建应用中所有的 state,然后这些 state 都会存放到这个被返回的 store 里。

现在我们的 User 组件就变成这样了:

import { Component, createRef } from 'react';
import store from '../store/store';
import { setUsernameAction } from '../store/user_action';
class User extends Component {
  inputRef = createRef();
  componentDidMount() {
    store.subscribe(() => {
      this.setState({});
    });
  }
  setUsername = () => {
    store.dispatch(setUsernameAction(this.inputRef.current.value));
  };
  render() {
    return (
      <div>
        <div>用户名: {store.getState().user.name}</div>
        <input ref={this.inputRef} type="text" />
        <button onClick={this.setUsername}>设置用户名</button>
      </div>
    );
  }
}
export default User;
  • store.getState() 可以拿到 state 对象,通过它,我们获取到其下我们需要的对象,比如 user 对象。
  • store.dispatch(action) 派发 action 对象,触发状态的更新。
  • store.subscribe(fn) 订阅状态的变化,执行回调函数。这里我们一发现状态发生了变化,就立刻重新渲染组件。

Redux 本质是发布订阅模式 ,状态集中在一起,状态可以通过  store.getState() 访问,通过  store.dispatch(action) 改变状态,通过  store.subscribe(fn) 订阅状态变化(React 组件监听到变化后,重新渲染组件)。

这种写法是最原始的写法,可以用在任何框架中。

缺点很明显:用到 redux 的组件要订阅 state 变化,一变化就重新渲染组件。 有时候其他组件的 state 变化了,当前组件也会进行不必要的重新渲染。

自己去判断吧,又太繁琐,容易写错,也容易忘记订阅。对于忘记订阅的问题,我们也可以直接把让根组件来监听和重新渲染,但这样性能很差。

接下来西瓜哥要讲的 React-Redux 库可以解决这个问题。它能够在当前组件用到的特定 state 发生改变时,才重新渲染组件。

React-Redux

发现大家都很喜欢在 React 里用 Redux,于是 Facebook 出了一个 React-Redux 库,让大家能够更好更正确地在 React 中使用 Redux。

React-Redux 配合 connect 高阶组件

我们先看看使用  connect 的写法。

demo:https://codesandbox.io/s/react-redux-with-connect-demo-kfvxt6。

React-Redux 引入了一个容器组件的概念,这个组件专门负责和 redux 打交道 。容器组件其实是一个高阶组件,将真正的 UI 组件做一个封装,在上面做了以下工作:

  • 将 state 和 dispatch 映射到 props,注入到 UI 组件中。
  • 监听 state 变化,必要时重新渲染 UI 组件。

高阶组件:一个函数,它会接收组件参数,然后返回一个新的组件。高阶组件的作用是对真正的 UI 组件做一些复用的逻辑的封装,通常用于做功能增强。

随着 React Hooks 愈发流行,大家现在更喜欢用 React Hooks 来取代高阶函数,写法更优雅。

const ContainerComponent = connect(
  mapStateToProps,
  mapDispatchToProps,
)(UIComponent);

现在开始改造项目。

我们创建一个 container 文件夹,里面放上 User.jsx 文件,里面写上如下内容:

// containers/User.jsx
import { connect } from 'react-redux';
import UserUI from '../components/User';
import { setUsernameAction } from '../store/user_action';
export default connect(
  // mapStateToProps
  (state) => ({ user: state.user }),
  // mapDispatchToProps
  (dispatch) => ({
    setUsername: (newName) => dispatch(setUsernameAction(newName))
  })
)(UserUI);

然后记得在使用该容器的地方,传入我们的 store 对象,如下:

import UserContainer from './containers/User';
import store from './store/store';
import './styles.css';
export default function App() {
  return <UserContainer store={store} />;
}

当然每个容器组件都要传入 store 未免太麻烦,我们通常会使用另一种做法:使用 redux-react 提供的 Context Provider,包裹住根组件,如下:

import { Provider } from 'react-redux';
Reactdom.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

然后是 UI 组件的改造:

import { Component, createRef } from 'react';
class User extends Component {
  inputRef = createRef();
  render() {
    return (
      <div>
        <div>用户名: {this.props.user.name}</div>
        <input ref={this.inputRef} type="text" />
        <button
          onClick={() => this.props.setUsername(this.inputRef.current.value)}
        >
          设置用户名
        </button>
      </div>
    );
  }
}
export default User;

UI 组件的 props 会拿到 user 对象、setUsername 方法以及我们注入的 store 对象(如果用 Context 的方式则取不到)。


使用了 connect 后,只有组件用到的 state 改变了,才会触发组件的更新。

这里有个需要特别注意的地方,就是你要 保证新的状态对象和旧状态不相等,这样才能触发组件重新渲染,这在处理对象方法时容易出错。你需要拷贝一个新的对象作为新的状态,推荐使用扩展运算符的写法。

// user_reducer.js
// 错误的写法,新的 state 依旧指向原来的对象
preState.name = action.payload;
return preState;
// 正确的写法
return { ...preState, name: action.payload };

或者可以考虑使用 immer 这种不可变数据结构库。

React-Rudex 配合 React Hooks

前面我们用了 connect 这么一个高阶组件,是为了给 UI 组件增强功能。

说到增强功能,react-redux 也提供了现在非常流行的 React Hooks 写法,写起来更优雅,也是目前西瓜哥我所在公司的做法。

这里我们就不需要 connect 高阶组件了,也就是说不需要容器组件。

demo:https://codesandbox.io/s/react-redux-with-hooks-demo-8ixl0h。

// User.js
import { useEffect, useRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { setUsernameAction } from '../store/user_action';
const User = () => {
  // 获取状态
  const user = useSelector((state) => state.user);
  // 获取 dipatch 方法
  const dipatch = useDispatch();
  const inputRef = useRef(null);
  return (
    <div>
      <div>用户名: {user.name}</div>
      <input ref={inputRef} type="text" />
      <button
        onClick={() => {
          const newName = inputRef.current.value;
          dipatch(setUsernameAction(newName));
        }}
      >
        设置用户名
      </button>
    </div>
  );
};
export default User;

通过 useSelector 我们可以拿到通过上下文绑定的 state,然后从中获取我们需要用到的状态。

const user = useSelector((state) => state.user);

如果有多个,我们可以写成对象的形式:

const { user, counter } = useSelector((state) => ({
  user: state.user,
  counter: state.counter
}));

是不是有点像 connect 的 mapStateToProps。

然后是获取 dispatch 方法:

const dipatch = useDispatch();

hook 非常优雅,但我也发现,相比 connect 写法,我们的 redux 状态逻辑和组件耦合在一起了。不过一般我们的组件都是业务组件,还是可以接受的。

Redux Toolkit

我们可以看到,我们要维护一个状态,我们要写 reducer 方法、action creator 方法,还要用一个 contants.js 文件集中式管理所有的 actionType 字符串。

你发现你写了非常多的 模板代码,每加一个 state 就要创建上面这些东西,各个文件里跑来跑去,人都麻了。

于是 redux 又出了一个工具集库 Redux Toolkit,来解决这个问题。

demo:https://codesandbox.io/s/redux-toolkit-demo-8x0391

Redux Toolkit 提供了 createSlice 方法,可以帮你用更少的代码生成配套的 reducer 和 action,而且有很好的可维护性。

// userSlice.js
import { createSlice } from '@reduxjs/toolkit';
const userSlice = createSlice({
  name: 'user',
  initialState: {
    name: '前端西瓜哥',
    age: 88
  },
  reducers: {
    setUsername: (state, action) => {
      // 因为 Redux Toolkit 内置使用了 immer,所以可以直接改。
      state.name = action.payload;
    }
  }
});
// actions
export const { setUsername } = userSlice.actions;
// 获取自己需要的 state,用在组件的 userSeletor hook 上。
export const selectUser = (state) => state.user;
// reducer
export default userSlice.reducer;

createSlice 传入 name(标识符,生成 actions 要用到)、initialState(初始值)、reducers(变成了对象形式)参数,然后返回一个对象。

这个返回的 slice 对象有 actions 对象属性,比如上面的代码,actions 下有一个 setUsername 的方法,执行后会返回  {type: "user/setUsername", payload: "新名字"} 。

可以看到 action 的 type 是根据 name 和 reducers 的属性生产的,确保唯一性。

slice 还有一个 reducer 对象,其实就是将前面传入的 reducers 配合自动生成的 action 转换为了函数的形式。

createSlice 干了什么事?createSlice 将原来管理一个状态但代码却是分离的 action 和 reducer 集中在了一起,不用自己去起 actionType 的名字。

然后是生成 store 也要改成 configureStore 的写法:

// store.js
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './userSlice';
const store = configureStore({
  reducer: {
    user: userReducer
  }
});
export default store;

总结

简单总结一下:

  • 原始写法,过于简陋,需要自己通过  store.subscribe(fn) 来判断一个组件是否要重新渲染,写起来麻烦、性能堪忧。
  • 配合 Redux React 库,通过 connect 来注入 redux 状态,要多写一个 connect 高阶组件生成的容器组件,但降低了耦合度。Redux React 会只在组件需要的状态改变时,重新渲染组件。这里要注意改变时,新旧状态不能相同,尤其是对象的情况,否则重新渲染不会工作。
  • 如果你的项目主要使用函数组件,可以不用 connect,直接用 useSelector 来获取状态,以及用 userDispatch 来改变状态。非常优雅。
  • Redux 又推出了 Redux Toolkit,解决了配置复杂、需要写太多模板、需要手动安装大量相关包的问题。
作者:前端西瓜哥
来源:https://www.toutiao.com/article/7105303246281261603

链接: https://www.fly63.com/article/detial/11702

React项目实战:react-redux-router基本原理

React项目实战:react-redux-router基本原理,Redux 的 React 绑定库包含了 容器组件和展示组件相分离 的开发思想。明智的做法是只在最顶层组件(如路由操作)里使用 Redux。其余内部组件仅仅是展示性的,所有数据都通过 props 传入。

react关于 Redux与flux的比较学习

flux四大元素:Dispatcher:根据注册派发动作(action),Store: 存储数据,处理数据,Action:用于驱动Dispatcher,View: 用户界面视图。flux的目的:纠正MVC框架的无法禁绝view与model通信的缺点。Redux基本原则:继承Flux基本原则:单向数据流

如何优雅地在React项目中使用Redux

Redux:状态管理工具,与React没有任何关系,其他UI框架也可以使用Redux,react-redux:React插件,作用:方便在React项目中使用Redux,react-thunk:中间件,作用:支持异步action

从 源码 谈谈 redux compose

compose,英文意思 组成,构成。它的作用也是通过一系列的骚操作,实现任意的、多种的、不同的功能模块的组合,用来加强组件。很容易实现功能的组合拼装、代码复用;可以根据需要组合不同的功能;

Redux与它的中间件:redux-thunk,redux-actions,redux-promise,redux-sage

这里要讲的就是一个Redux在React中的应用问题,讲一讲Redux,react-redux,redux-thunk,redux-actions,redux-promise,redux-sage这些包的作用和他们解决的问题。

redux中间件的原理_深入理解 Redux 中间件

最近几天对 redux 的中间件进行了一番梳理,又看了 redux-saga 的文档,和 redux-thunk 和 redux-promise 的源码,结合前段时间看的redux的源码的一些思考,感觉对 redux 中间件的有了更加深刻的认识,因此总结一下

react-redux 的使用

类似于 Vue,React 中组件之间的状态管理 第三方包为:react-redux。react-redux 其实是 Redux的官方React绑定库,它能够使你的React组件从Redux store中读取数据,并且向store分发actions以更新数据。

如何使用useReducer Hook?

看到“reducer”这个词,容易让人联想到Redux,但是在本文中,不必先理解Redux才能阅读这篇文章。咱们将一起讨论“reducer”实际上是什么,以及如何利用useReducer来管理组件中的复杂状态,以及这个新钩子对Redux意味着什么?

带着问题看React-Redux源码

我在读React-Redux源码的过程中,很自然的要去网上找一些参考文章,但发现这些文章基本都没有讲的很透彻,很多时候就是平铺直叙把API挨个讲一下,而且只讲某一行代码是做什么的,却没有结合应用场景和用法解释清楚为什么这么做。

React Hooks 是不能替代 Redux 的

我的许多同事最近通过各种方式问同一类问题:“如果我们开始用 hook 后,那还有必要用 Redux 吗?”“React hook 不是让 Redux 过时了吗?那只用 Hooks 就可以做 Redux 所有能做的事了吧?”

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!