Redux的使用
简介
Redux 是一个使用叫做“action”的事件来管理和更新应用状态的模式和工具库 它以集中式Store(centralized store)的方式对整个应用中使用的状态进行集中管理,其规则确保状态只能以可预测的方式更新。
为什么要使用 Redux?Redux 帮你管理“全局”状态 - 哪些应用程序的许多部分都需要的状态。
Redux 提供的模式和工具使您更容易理解应用程序中的状态何时、何地、为什么以及如何更新,以及当这些更改发生时您的应用程序逻辑将如何表现。 Redux 指导您编写可预测和可测试的代码,这有助于让您确信您的应用程序将按预期工作。
不像Vuex,Redux是一个独立的工具库,不仅可以在React中使用,还可以在其他框架中使用,甚至可以直接用script标签的形式在页面中直接使用
Redux使用
我们就通过一个简单的计数器页面来了解Redux该怎么使用,我们先不在react项目中使用,先看看最原始的redux要怎么用。后面再看看怎么在react中使用。
基础的html页面如下,需要引用redux的umd包(使用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全局对象,我们首先需要定义一个reducer,reducer 是一个函数,接收当前的 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
发现上面有dispatch、getState、replaceReducer、subscribe这四个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中根据action的type值,来判断需要怎么处理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中就会按照action的type类型,去更新state,点击按钮-也是一样
我们看看效果
store.subscribe
此时state是确实被更新了,但是页面为什么没有改变呢?这是因为state确实是改变了,但是redux并没有告诉我state改变了啊,所以我们需要知道state变化了,再执行一些动作。此时就可以使用store的subscribe来订阅一个监听器,一旦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 函数作为 value 的 object,合并成一个最终的 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())

可以看到我们组合了countReducer和priceReducer,最终会按照传入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 中使用 Redux,react-redux 会提供 dispatch 函数让你直接调用它 。
把一个 value 为不同 action creator 的对象,转成拥有同名 key 的对象。同时使用 dispatch 对每个 action creator 进行包装,以便可以直接调用它们。
惟一会使用到 bindActionCreators 的场景是当你需要把 action creator 往下传到一个组件上,却不想让这个组件觉察到 Redux 的存在,而且不希望把 dispatch 或 Redux store 传给它。
为方便起见,你也可以传入一个函数作为第一个参数,它会返回一个函数。
上面的解释理解起来可能有些难以理解,下面我们用例子说明:
什么是action creator,就是返回action的函数,例如:
const incrementActionCreator = () => {
return { type: 'COUNT/INCREMENT' }
}
定义了如上action creator,然而我们派发action的时候仍然需要使用store.dispatch进行派发,例如store.dispatch(incrementActionCreator()),这样写未免有些累赘了,此时我们就可以使用bindActionCreators方法,将action creator和store.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