前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >React 进阶 - React Redux

React 进阶 - React Redux

作者头像
Cellinlab
发布2023-05-17 20:53:46
发布2023-05-17 20:53:46
1.2K00
代码可运行
举报
文章被收录于专栏:Cellinlab's BlogCellinlab's Blog
运行总次数:0
代码可运行

# 状态管理应用场景

# 组件间共用数据

应用初始化时候,只请求一次数据,然后通过状态管理把数据存起来,需要数据的组件只需要从状态管理中‘拿’就可以了。

# 复杂组件之间通信

对于 SPA 单页面应用一切皆组件,对于嵌套比较深的组件,组件通信成了一个棘手的问题。如如下的场景, B 组件向 H 组件传递某些信息,那么常规的通信方式似乎难以实现。

这个时候状态管理就派上用场了,可以把 B 组件的信息传递给状态管理层,H 组件连接状态管理层,再由状态管理层通知 H 组件,这样就本质解决了组件通信问题。

# React-Redux,Redux,React 三者关系

  • Redux
    • Redux 是一个应用状态管理 js 库,它本身和 React 是没有关系的
    • Redux 可以应用于其他框架构建的前端应用,甚至也可以应用于 Vue 中
  • React-Redux
    • React-Redux 是连接 React 应用和 Redux 状态管理的桥梁
    • React-redux 主要做两件事
      • 如何向 React 应用中注入 redux 中的 Store
      • 如何根据 Store 的改变,把消息派发给应用中需要状态的每一个组件
  • React
    • React 是一个前端框架,它本身和 Redux 也是没有关系的

# Redux

# 三大原则

  • 单向数据流
    • 整个 Redux ,数据流向都是单向的
  • state 只读
    • 在 Redux 中不能通过直接改变 state ,来让状态发生变化,如果想要改变 state ,那就必须触发一次 action ,通过 action 执行每个 reducer
  • 纯函数执行
    • 每一个 reducer 都是一个纯函数,里面不要执行任何副作用,返回的值作为新的 statestate 改变会触发 store 中的 subscribe

# 发布订阅思想

Redux 可以作为发布订阅模式的一个具体实现。

Redux 都会创建一个 store ,里面保存了状态信息,改变 store 的方法 dispatch ,以及订阅 store 变化的方法 subscribe

# 中间件思想

Redux 应用了前端领域为数不多的中间件 compose ,Redux 的中间件用来强化 dispatch , Redux 提供了中间件机制,使用者可以根据需要来强化 dispatch 函数,传统的 dispatch 是不支持异步的,但是可以针对 Redux 做强化,于是有了 redux-thunk,redux-actions 等中间件,包括 dvajs 中,也写了一个 Redux 支持 promise 的中间件。

代码语言:javascript
代码运行次数:0
运行
复制
const compose = (...funcs) => {
  return funcs.reduce((f, g) => (x) => f(g(x)))
}

funcs 为中间件组成的数组,compose 通过数组的 reduce 方法,实现执行每一个中间件,强化 dispatch

# 核心 API

createStore

  • 通过 createStore 可以创建一个 Store ,使用者可以将这个 Store 保存传递给 React 应用
代码语言:javascript
代码运行次数:0
运行
复制
const store = createStore(reducer, initialState, middleware)

  • reducer 是一个纯函数,用来处理 action ,返回新的 state
    • 如果有多个 reducer ,可以使用 combineReducers 来合并
  • initialState 是初始状态
  • middleware,如果有中间件,可以在这里传入

combineReducers

代码语言:javascript
代码运行次数:0
运行
复制
const rootReducer = combineReducers({
  count: countReducer,
  user: userReducer,
})

applyMiddleware

代码语言:javascript
代码运行次数:0
运行
复制
const middleware = applyMiddleware(logMiddleware)

复制🤏

  • applyMiddleware,用于注册中间件,可以将多个中间件组合成一个中间件
  • 每次触发 action 中间件依次执行

# Redux 基本用法

编写 reducer

代码语言:javascript
代码运行次数:0
运行
复制
function countReducer(state = 0, action) {
  switch (action.type) {
    case "ADD":
      return state + 1
    case "MINUS":
      return state - 1
    default:
      return state
  }
}
function InfoReducer(state = {}, action) {
  const { payload = {} } = action
  switch (action.type) {
    case "SET":
      return { ...state, ...payload }
    default:
      return state
  }
}

注册中间件

代码语言:javascript
代码运行次数:0
运行
复制
function logMiddleware() {
  return (next) => {
    return (action) => {
      const { type } = action
      console.log("dispatch ", type)
      return next(action)
    }
  }
}

  • Redux 的中间件的编写方式,本质上应用了函数柯里化

生成 Store

代码语言:javascript
代码运行次数:0
运行
复制
const rootMiddleware = applyMiddleware(logMiddleware)

const rootReducer = combineReducers({
  count: countReducer,
  info: InfoReducer,
})

const Store = createStore(
  rootReducer,
  {
    count: 0,
    info: {
      name: "",
      age: 18,
    },
  },
  rootMiddleware
)

使用 Redux

代码语言:javascript
代码运行次数:0
运行
复制
function Index() {
  const [state, changeState] = useState(Store.getState())
  useEffect(() => {
    const unSubscribe = Store.subscribe(() => {
      changeState(Store.getState())
    })
    return () => {
      unSubscribe()
    }
  }, [])

  return (
    <div>
      <h1>{state.count}</h1>
      <button onClick={() => Store.dispatch({ type: "ADD" })}>+</button>
      <button onClick={() => Store.dispatch({ type: "MINUS" })}>-</button>
      <p>{state.info.name}</p>
      <p>{state.info.age}</p>
      <button
        onClick={() => Store.dispatch({ type: "SET", payload: { name: "cellin", age: 18 } })}
      >
        set info
      </button>
    </div>
  )
}

存在的问题:

  • 上述 Demo 无法满足状态共用的情况
  • 正常情况不可能将每一个需要状态的组件都用 subscribe / unSubscribe 来进行订阅
  • 比如 A 组件需要状态 aB 组件需要状态 b ,那么改变 a,只希望 A 组件更新,不希望 B 组件更新,显然上述是不能满足的

所以为了解决上述诸多问题,React-Redux 就应运而生了。

# React-Redux 用法

React-Redux 是沟通 React 和 Redux 的桥梁,它主要功能体现在如下两个方面:

  • 接受 ReduxStore,并把它合理分配到所需要的组件中
  • 订阅 Storestate 的改变,促使消费对应的 state 的组件更新

# Provider

由于 Redux 数据层,可能被很多组件消费,所以 React-Redux 中提供了一个 Provider 组件,可以全局注入 Redux 中的 store ,所以使用者需要把 Provider 注册到根部组件中。

Provider 作用就是保存 Redux 中的 store ,分配给所有需要 state 的子孙组件。

代码语言:javascript
代码运行次数:0
运行
复制
export default function Root() {
  return (
    <Provider store={store}>
      <App />
    </Provider>
  )
}

# connect

React-Redux 提供了一个高阶组件 connect ,被 connect 包装后组件将获得如下功能:

  • 能够从 props 中获取改变 state 的方法 Store.dispatch
  • 如果 connect 有第一个参数,那么会将 Redux state 中的数据,映射到当前组件的 props 中,子组件可以使用消费
  • 当需要的 state ,有变化的时候,会通知当前组件更新,重新渲染视图

可以利用 connect 提供的功能,做数据获取,数据通信,状态派发等操作。

代码语言:javascript
代码运行次数:0
运行
复制
function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)

复制🤏

mapStateToProps

  • 组件依赖 Redux 的 state,映射到业务组件的 props 中,state 改变触发,业务组件 props 改变,触发业务组件更新视图
  • 当这个参数没有的时候,当前组件不会订阅 store 的改变
代码语言:javascript
代码运行次数:0
运行
复制
const mapStateToProps = (state) => ({ count: state.count })

mapDispatchToProps

  • 将 Redux 中的 dispatch 方法,映射到业务组件的 props
代码语言:javascript
代码运行次数:0
运行
复制
const mapDispatchToProps = (dispatch) => ({
  addCount: () => dispatch({ type: "ADD" }),
  setInfo: (payload) => dispatch({ type: "SET", payload }),
})

mergeProps

正常情况下,如果没有这个参数,会按照如下方式进行合并,返回对象

代码语言:javascript
代码运行次数:0
运行
复制
const mergeProps = (stateProps, dispatchProps, ownProps) => ({
  ...stateProps,
  ...dispatchProps,
  ...ownProps,
})

可以自定义的合并规则,还可以附加一些属性

代码语言:javascript
代码运行次数:0
运行
复制
const mergeProps = (stateProps, dispatchProps, ownProps) => ({
  ...stateProps,
  ...dispatchProps,
  ...ownProps,
  name: "cellin",
})

options

代码语言:javascript
代码运行次数:0
运行
复制
{
  context?: Object,
  pure?: boolean,
  areStatesEqual?: Function,
  areOwnPropsEqual?: Function,
  areStatePropsEqual?: Function,
  areMergedPropsEqual?: Function,
  forwardRef?: boolean,
}

# React-Redux 实现状态共享

通过在根组件中注入 store ,并在 useEffect 中改变 state 内容

代码语言:javascript
代码运行次数:0
运行
复制
export default function Root() {
  useEffect(() => {
    Store.dispatch({ type: "ADD" })
    Store.dispatch({ type: "SET", payload: { name: "cellin", age: 18 } })
  }, [])
  return (
    <Provider store={Store}>
      <Index />
    </Provider>
  )
}

在整个应用中在想要获取数据的组件里,获取 state 中的内容

代码语言:javascript
代码运行次数:0
运行
复制
import { connect } from "react-redux"

class Index extends React.Component {
  componentDidMount() {
    console.log(this.props)
  }
  render() {
    const { info, number } = this.props
    return (
      <div>
        <h1>{number}</h1>
        <p>{info.name}</p>
        <p>{info.age}</p>
      </div>
    )
  }
}

const mapStateToProps = (state) => ({
  number: state.count,
  info: state.info,
})

export default connect(mapStateToProps)(Index)

通过 mapStateToProps 获取指定 state 中的内容,然后渲染视图

# React-Redux 实现组件通信

代码语言:javascript
代码运行次数:0
运行
复制
function ComponentA({ toCompB, compBSend }) {
  const [compASend, setCompASend] = useState("")

  return (
    <div>
      <p> CompA </p>
      <p>from CompB: {compBSend}</p>
      <input type="text" value={compASend} onChange={(e) => setCompASend(e.target.value)} />
      <button onClick={() => toCompB(compASend)}>send to CompB</button>
    </div>
  )
}

const compAMapStateToProps = (state) => ({
  compBSend: state.compBSend,
})

const compAMapDispatchToProps = (dispatch) => ({
  toCompB: (msg) => dispatch({ type: "SET", payload: { compASend: msg } }),
})

export const CompA = connect(compAMapStateToProps, compAMapDispatchToProps)(ComponentA)

代码语言:javascript
代码运行次数:0
运行
复制
class ComponentB extends React.Component {
  state = { compBSend: "" }
  handleToA = () => {
    this.props.dispatch({
      type: "SET",
      payload: { compBSend: this.state.compBSend },
    })
  }
  componentDidMount() {
    console.log(this.props)
  }
  render() {
    const { compASend } = this.props
    return (
      <div>
        <p> CompB </p>
        <p>from CompA: {compASend}</p>
        <input
          type="text"
          value={this.state.compBSend}
          onChange={(e) => this.setState({ compBSend: e.target.value })}
        />
        <button onClick={this.handleToA}>send to CompA</button>
      </div>
    )
  }
}

const compBMapStateToProps = (state) => ({
  compASend: state.compASend,
})

export const CompB = connect(compBMapStateToProps)(ComponentB)

# React-Redux 原理

# Provider 注入 Store

代码语言:javascript
代码运行次数:0
运行
复制
/* react-redux/src/components/Provider.js */
const ReactReduxContext = React.createContext(null)

function Provider({ store, context, children }) {
  const contextValue = useMemo(() => {
    const subscription = new Subscription(store)
    return {
      store,
      subscription,
    }
  }, [store])

  useEffect(() => {
    const { subscription } = contextValue
    subscription.trySubscribe()
    return () => {
      subscription.tryUnsubscribe()
    }
  }, [contextValue])
  const Context = ReactReduxContext
  return <Context.Provider value={contextValue}>{children}</Context.Provider>
}

  1. React-Redux 是通过 context 上下文来保存传递 Store 的,但是上下文 value 保存的除了 Store 还有 subscription
  2. subscription 可以理解为订阅器
    • 在 React-redux 中一方面用来订阅来自 state 变化,另一方面通知对应的组件更新
    • 在 Provider 中的订阅器 subscription 为根订阅器
  3. ProvideruseEffect 中,进行真正的绑定订阅功能,其原理内部调用了 store.subscribe ,只有根订阅器才会触发 store.subscribe

# Subscription 订阅器

代码语言:javascript
代码运行次数:0
运行
复制
/* react-redux/src/utils/Subscription.js */
export default class Subscription {
  constructor(store, parentSub) {}

  addNestedSub(listener) {
    this.trySubscribe()
    return this.listeners.subscribe(listener)
  }

  notifyNestedSubs() {
    this.listeners.notify()
  }

  isSubscribed() {
    return Boolean(this.unsubscribe)
  }

  trySubscribe() {
    if (!this.unsubscribe) {
      this.unsubscribe = this.parentSub
        ? this.parentSub.addNestedSub(this.handleChangWrapper)
        : this.store.subscribe(this.handleChangeWrapper)
      this.listeners = createListenerCollection()
    }
  }

  tryUnsubscribe() {}
}

整个订阅器的核心:层层订阅,上订下发

  • 层层订阅
    • React-Redux 采用了层层订阅的思想
    • 每一个用 connect 包装的组件,内部也有一个 Subscription ,而且这些订阅器一层层建立起关联,Provider 中的订阅器是最根部的订阅器,可以通过 trySubscribeaddNestedSub 方法可以看到
    • 如果父组件是一个 connect ,子孙组件也有 connect ,那么父子 connectSubscription 也会建立起父子关系
  • 上订下发
    • 在调用 trySubscribe 的时候,能够看到订阅器会和上一级的订阅器通过 addNestedSub 建立起关联
    • storestate 发生改变,会触发 store.subscribe ,但是只会通知给 Provider 中的根 Subscription,根 Subscription 也不会直接派发更新,而是会下发给子代订阅器( connect 中的 Subscription ),再由子代订阅器,决定是否更新组件,层层下发

# connect 控制更新

  1. connect 中有一个 selector 的概念,他通过 mapStateToPropsmapDispatchToProps ,把 Redux 中 state 状态合并到 props 中,得到最新的 props
  2. 每一个 connect 都会产生一个新的 Subscription ,和父级订阅器建立起关联,这样父级会触发子代的 Subscription 来实现逐层的状态派发
  3. Subscription 通知的是 checkForUpdates 函数,checkForUpdates 会形成新的 props ,与之前缓存的 props 进行浅比较,如果不想等,那么说明 state 已经变化了,直接触发一个 useReducer 来更新组件,如果相等,那么当前组件不需要更新,直接通知子代 Subscription ,检查子代 Subscription 是否更新,完成整个流程

# Redux 实现异步

  • redux-thunk
  • redux-saga
  • dvajs
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022/9/22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • # 状态管理应用场景
    • # 组件间共用数据
    • # 复杂组件之间通信
  • # React-Redux,Redux,React 三者关系
  • # Redux
    • # 三大原则
    • # 发布订阅思想
    • # 中间件思想
    • # 核心 API
    • # Redux 基本用法
  • # React-Redux 用法
    • # Provider
    • # connect
    • # React-Redux 实现状态共享
    • # React-Redux 实现组件通信
  • # React-Redux 原理
    • # Provider 注入 Store
    • # Subscription 订阅器
    • # connect 控制更新
  • # Redux 实现异步
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档