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