Redux的使用

  1. Redux的使用
  2. 简介
  3. Redux使用
  4. store.getState()
  5. store.dispatch
  6. store.subscribe
  7. Redux.combineReducers
  8. Redux.applyMiddleware
  9. Redux.bindActionCreators

Redux的使用

简介

Redux 是一个使用叫做“action”的事件来管理和更新应用状态的模式和工具库 它以集中式Storecentralized store)的方式对整个应用中使用的状态进行集中管理,其规则确保状态只能以可预测的方式更新。

为什么要使用 Redux
Redux 帮你管理“全局”状态 - 哪些应用程序的许多部分都需要的状态。

Redux 提供的模式和工具使您更容易理解应用程序中的状态何时、何地、为什么以及如何更新,以及当这些更改发生时您的应用程序逻辑将如何表现。 Redux 指导您编写可预测和可测试的代码,这有助于让您确信您的应用程序将按预期工作。

不像VuexRedux是一个独立的工具库,不仅可以在React中使用,还可以在其他框架中使用,甚至可以直接用script标签的形式在页面中直接使用

Redux使用

我们就通过一个简单的计数器页面来了解Redux该怎么使用,我们先不在react项目中使用,先看看最原始的redux要怎么用。后面再看看怎么在react中使用。

基础的html页面如下,需要引用reduxumd包(使用jquery来方便我们操作dom

<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="https://cdn.bootcss.com/redux/4.0.0/redux.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
</head>

<body>
  <div id="root">
    <p><span id="count"></span></p>
    <button>+</button>
    <button>-</button>
  </div>
</body>

</html>

引用了redux,会在Window上挂载一个Redux全局对象,我们首先需要定义一个reducerreducer 是一个函数,接收当前的 state 和一个 action 对象,必要时决定如何更新状态,并返回新状态。函数签名是:(state, action) => newState。 你可以将 reducer 视为一个事件监听器,它根据接收到的 action(事件)类型处理事件。

$(document).ready(function() {

  const reducer = (state = {count : 0}, action) => {
    switch (action.type) {
      case 'INCREMENT':
        return {...state, count: state.count + 1}
      case 'DECREMENT':
        return {...state, count: state.count - 1}  
      default:
        return state 
    }
  }

})

action 是一个具有 type 字段的普通 JavaScript 对象。你可以将 action 视为描述应用程序中发生了什么的事件。type 字段是一个字符串,给这个 action 一个描述性的名字,比如"todos/todoAdded"。我们通常把那个类型的字符串写成“域/事件名称”,其中第一部分是这个 action 所属的特征或类别,第二部分是发生的具体事情。

action 对象可以有其他字段,其中包含有关发生的事情的附加信息。按照惯例,我们将该信息放在名为 payload 的字段中。

在定义reducer的时候,注意返回的状态必须是一个全新的状态,不能直接使用和之前相同的引用,例如使用obj.a直接修改a的值,或者array.push一个新的值

此时有了reducer,我们就可以使用全局的Redux创建一个store

$(document).ready(function() {

  const reducer = (state = {count : 0}, action) => {
    switch (action.type) {
      case 'INCREMENT':
        return {...state, count: state.count + 1}
      case 'DECREMENT':
        return {...state, count: state.count - 1}  
      default:
        return state 
    }
  }

  const store = Redux.createStore(reducer)
  console.log(store)

})

我们可以看看store上有哪些API

发现上面有dispatchgetStatereplaceReducersubscribe这四个API,下面我们都会用到。

store.getState()

通过上图可以发现,我们的页面count字段是没值的,那我们要怎么获取到store中的state呢?此时就可以使用store.getState()来获取store中的状态了,我们获取到state然后渲染到页面中

$(document).ready(function() {

  const reducer = (state = {count : 0}, action) => {
    switch (action.type) {
      case 'INCREMENT':
        return {...state, count: state.count + 1}
      case 'DECREMENT':
        return {...state, count: state.count - 1}  
      default:
        return state 
    }
  }

  const store = Redux.createStore(reducer)
  console.log(store.getState()) // {count: 0}
  $('#count').text(store.getState().count)

})

store.dispatch

此时页面就已经有了count的值了,那怎么修改state呢?此时我们就可以使用dispatch来派发一个action,然后在reducer中根据actiontype值,来判断需要怎么处理state

$(document).ready(function() {

  // ...

  $('button').eq(0).click(function() {
    store.dispatch({type: 'INCREMENT'})
    console.log(store.getState())
  })

  $('button').eq(1).click(function() {
    store.dispatch({type: 'DECREMENT'})
    console.log(store.getState())
  })

})

我们点击按钮+,通过store dispatch 一个action {type: 'INCREMENT'},然后reducer中就会按照actiontype类型,去更新state,点击按钮-也是一样

我们看看效果

store.subscribe

此时state是确实被更新了,但是页面为什么没有改变呢?这是因为state确实是改变了,但是redux并没有告诉我state改变了啊,所以我们需要知道state变化了,再执行一些动作。此时就可以使用storesubscribe来订阅一个监听器,一旦state改变了,redux就去执行那个监听器,做相应的更改。

subscribe接收一个函数作为参数,我们将我们需要做的操作传进去,一旦redux检测到state发生了变化,就会去调用这个函数

$(document).ready(function() {
  // ...

  store.subscribe(function() {
    $('#count').text(store.getState().count)
  })

})

subscribe还接收一个返回值,用来取消订阅,这个返回值是一个方法,我们直接调用这个方法就可以取消订阅了,后续state的修改不会去触发监听器函数,但是redux内部state的状态还是会持续更新

<!DOCTYPE html>
<html lang="zh-CN">
<!-- ... -->
<body>
  <div id="root">
    <p><span id="count"></span></p>
    <button>+</button>
    <button>-</button>
    <button>取消订阅</button>
  </div>
  <script>
    
    $(document).ready(function() {

      // ...

      const cancelSub = store.subscribe(function() {
        $('#count').text(store.getState().count)
      })

      $('button').eq(2).click(function() {
        cancelSub()
      })

    })
  </script>
</body>

</html>

最主要的几个API我们已经用到了,剩下的几个api我们分别来说说

Redux.combineReducers

由于不建议创建多个store,因此如果有多个reducer,需要利用这个API来组合reducer

combineReducers 辅助函数的作用是,把一个由多个不同 reducer 函数作为 valueobject,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore 方法。

合并后的 reducer 可以调用各个子 reducer,并把它们返回的结果合并成一个 state 对象。 由 combineReducers() 返回的 state 对象,会将传入的每个 reducer 返回的 state 按其传递给 combineReducers() 时对应的 key 进行命名。

const countReducer = (state = {count : 0}, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return {...state, count: state.count + 1}
    case 'DECREMENT':
      return {...state, count: state.count - 1}  
    default:
      return state 
  }
}

const priceReducer = (state = {price : 10}, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return {...state, price: state.price + 1}
    case 'DECREMENT':
      return {...state, price: state.price - 1}  
    default:
      return state 
  }
}

const combinedReducers = Redux.combineReducers({countReducer: countReducer, priceReducer: priceReducer})

const store = Redux.createStore(combinedReducers)
console.log(store.getState())


可以看到我们组合了countReducerpriceReducer,最终会按照传入combineReducers对象的key进行命名区分,因此我们之前的代码需要改为

$('#count').text(store.getState().countReducer.count)

const cancelSub = store.subscribe(function() {
  $('#count').text(store.getState().countReducer.count)
})

我们发现一个问题,为什么我派发了一个action,两个state都会发生变化?其实是因为我们的action都是INCREMENT,由于两个reducer中的type设置的是一样的,这样的话就会导致这个问题,一般为了解决这个问题,我们都是把那个类型的字符串写成“域/事件名称”,例如COUNT/INCREMENT,用于区分不同的reducer

再修改一下代码

// ...
const countReducer = (state = {count : 0}, action) => {
  switch (action.type) {
    case 'COUNT/INCREMENT':
      return {...state, count: state.count + 1}
    case 'COUNT/DECREMENT':
      return {...state, count: state.count - 1}  
    default:
      return state 
  }
}

const priceReducer = (state = {price : 10}, action) => {
  switch (action.type) {
    case 'PRICE/INCREMENT':
      return {...state, price: state.price + 1}
    case 'PRICE/DECREMENT':
      return {...state, price: state.price - 1}  
    default:
      return state 
  }
}

// ...
$('button').eq(0).click(function() {
  store.dispatch({type: 'COUNT/INCREMENT'})
  console.log(store.getState())
})

$('button').eq(1).click(function() {
  store.dispatch({type: 'COUNT/INCREMENT'})
  console.log(store.getState())
})

现在就正常了

Redux.applyMiddleware

我们可以在Redux中使用各种插件,以增强我们的功能,比如我在每次修改state的时候,不用自己手动打印,而是自动打印,此时我们可以使用redux-logger插件

<script src="https://cdn.bootcdn.net/ajax/libs/redux-logger/4.0.0/redux-logger.js"></script>

<script>
  // ...
  const logger = reduxLogger.createLogger()
  const store = Redux.createStore(combinedReducers, Redux.applyMiddleware(logger))
</script>

Redux.bindActionCreators

一般情况下你可以直接在 Store 实例上调用 dispatch。如果你在 React 中使用 Reduxreact-redux 会提供 dispatch 函数让你直接调用它 。

把一个 value 为不同 action creator 的对象,转成拥有同名 key 的对象。同时使用 dispatch 对每个 action creator 进行包装,以便可以直接调用它们。

惟一会使用到 bindActionCreators 的场景是当你需要把 action creator 往下传到一个组件上,却不想让这个组件觉察到 Redux 的存在,而且不希望把 dispatchRedux store 传给它。

为方便起见,你也可以传入一个函数作为第一个参数,它会返回一个函数。

上面的解释理解起来可能有些难以理解,下面我们用例子说明:

什么是action creator,就是返回action的函数,例如:

const incrementActionCreator = () => {
  return { type: 'COUNT/INCREMENT' }
}

定义了如上action creator,然而我们派发action的时候仍然需要使用store.dispatch进行派发,例如store.dispatch(incrementActionCreator()),这样写未免有些累赘了,此时我们就可以使用bindActionCreators方法,将action creatorstore.diapatch传进去,返回一个函数,可以直接派发 action,例如:

const incrementActionCreator = () => {
  return { type: 'COUNT/INCREMENT' }
}

const increment = Redux.bindActionCreators(incrementActionCreator, store.diapatch)

increment() // 这里直接会派发action

同时,Redux.bindActionCreators的第一个参数还可以接受一个由action creator组成的对象,对象的key随便指定,此时会返回和key相同的一组对象,对象的值就是可以直接派发action的函数,例如:

const incrementActionCreator = () => {
  return { type: 'COUNT/INCREMENT' }
}
const decrementActionCreator = () => {
  return { type: 'COUNT/DECREMENT' }
}

let actions = Redux.bindActionCreators({ increment: incrementActionCreator, decrement: decrementActionCreator }, store.dispatch)

// 此时使用这种方式直接派发
actions.increment()
actions.decrement()

我们将之前的例子改写成这种形式,注意下面使用了两种形式,可以更好地看出区别

// ...
const incrementActionCreator = () => {
  return { type: 'COUNT/INCREMENT' }
}
const decrementActionCreator = () => {
  return { type: 'COUNT/DECREMENT' }
}

let increment = Redux.bindActionCreators(incrementActionCreator, store.dispatch)
let actions = Redux.bindActionCreators({ increment: incrementActionCreator, decrement: decrementActionCreator }, store.dispatch)

$('button').eq(0).click(function() {
  increment()
})

$('button').eq(1).click(function() {
  actions.decrement()
})


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 289211569@qq.com