Redux的核心理念
Store- 存储数据
// 初始化的数据
const initialState = {
name: "kobe",
counter: 100
}
action- Redux要求我们通过
action来更新数据:- 所有数据的变化,必须通过派发(dispatch)
action来更新; - action是一个普通的JavaScript对象,用来描述这次更新的
type和content;
- 所有数据的变化,必须通过派发(dispatch)
- Redux要求我们通过
// 修改store中的数据: 必须action
const nameAction = { type: "change_name", name: "kobe" }
store.dispatch(nameAction)
reducer- 如何将
state和action联系在一起呢?--- reducerreducer是一个纯函数;reducer做的事情就是将传入的state和action结合起来生成一个新的state;
- 如何将
// 定义reducer函数: 纯函数
// 两个参数:
// 参数一: store中目前保存的state
// 参数二: 本次需要更新的action(dispatch传入的action)
// 返回值: 它的返回值会作为store之后存储的state
function reducer(state = initialState, action) {
switch(action.type) {
case "change_name":
return { ...state, name: action.name }
case "add_number":
return { ...state, counter: state.counter + action.num }
default:
// 没有新数据更新, 那么返回之前的state
return state
}
}
核心的原则有哪些?(面试)
详细版
- 单一数据源
- 整个应用程序的state被存储在一颗
object tree中,并且这个object tree只存储在一个store中: - Redux并没有强制让我们不能创建多个
Store,但是那样做并不利于数据的维护; - 单一的数据源可以让整个应用程序的
state变得方便维护、追踪、修改;
- 整个应用程序的state被存储在一颗
State是只读的- 唯一修改
State的方法一定是触发action,不要试图在其他地方通过任何的方式来修改State: - 这样就确保了
View或网络请求都不能直接修改state,它们只能通过action来描述自己想要如何修改state; - 这样可以保证所有的修改都被集中化处理,并且按照严格的顺序来执行,所以不需要担心
race condition(竟态)的问题;- 竟态(Race Condition)指的是在多线程或多进程的程序中,由于执行顺序的不确定性,导致不同的线程在对共享资源进行读写操作时,出现了不可预测的、混乱的结果。竟态可能导致程序崩溃、数据损坏等问题。
- 唯一修改
- 使用纯函数来执行修改
- 通过
reducer将 旧state和action联系在一起,并且返回一个新的State: - 随着应用程序的复杂度增加,我们可以将
reducer拆分成多个小的reducer,分别操作不同state tree的一部分; - 但是所有的
reducer都应该是纯函数,不能产生任何的副作用;
- 通过
简略版
Redux 的核心理念是:单一数据源、状态只读、使用纯函数来执行状态修改。
- 单一数据源:
Redux应用的状态被存储在单一对象树中,这个对象被称作“状态树”,这让整个应用的状态变得可预测,也方便状态管理。 - 状态只读:
Redux中的状态是只读的,唯一改变状态的方式是通过触发action,action是一个描述状态变化的纯对象。 - 使用纯函数来执行状态修改:为了让应用状态更可控,
Redux要求修改状态的函数(称作reducer)是一个纯函数,即给定同样的输入,总是返回同样的输出,不产生任何副作用。这样能够确保状态修改的可测试性和可维护性。
Redux的使用过程
- 创建一个对象,作为我们要保存的状态:
// 初始化的数据
const initialState = {
name: "kobe",
counter: 100
}
- 创建
Store来存储这个state- 创建
store时必须创建reducer; - 我们可以通过
store.getState来获取当前的state;
- 创建
// 定义reducer函数: 纯函数
// 两个参数:
// 参数一: store中目前保存的state
// 参数二: 本次需要更新的action(dispatch传入的action)
// 返回值: 它的返回值会作为store之后存储的state
function reducer(state = initialState, action) {
// 有新数据进行更新的时候, 那么返回一个新的state
if (action.type === "change_name") {
return { ...state, name: action.name }
} else if (action.type === "add_number") {
return { ...state, counter: state.counter + action.num }
}
// 没有新数据更新, 那么返回之前的state
return state
}
// 创建的store
const store = createStore(reducer)
- 通过
action来修改state- 通过
dispatch来派发action; - 通常
action中都会有type属性,也可以携带其他的数据;
- 通过
// 修改store中的数据: 必须action
const nameAction = { type: "change_name", name: "kobe" }
store.dispatch(nameAction)
//store.getState()是 Redux store 中的一个方法,用于获取当前存储的完整 state 树。
//它返回的是一个对象,该对象包含整个应用程序的状态。
//因为 Redux 的状态是不可变的,所以你不能改变这个对象,只能读取它。
console.log(store.getState())
- 修改
reducer中的处理代码-
这里一定要记住,
reducer是一个纯函数,不需要直接修改state; -
后面我会讲到直接修改
state带来的问题;- 因为
reducer函数被设计为纯函数,它们的行为应该只依赖于它们的输入参数,而不依赖于外部环境的状态。如果在reducer函数中直接修改了state对象,意味着函数的输出结果不仅仅取决于输入参数,还依赖于外部的状态,这破坏了纯函数的定义,违反了Redux中reducer的设计思想。因此,不要直接修改state参数,而是通过返回一个新的state对象来实现状态更新。
- 因为
-
假设有以下的reducer函数代码:
function reducer(state, action) {
state.count++;
return state;
}
在上面的reducer函数中,它直接修改了state对象的count属性,然后直接返回state对象。这种方式修改state破坏了纯函数的定义,并且也不遵守Redux对reducer函数的约束。这是因为函数的输出结果不仅仅取决于输入参数(即state和action),还依赖于外部状态(state对象中的count属性)。由于这种方式修改state对象后会导致state对象被改变,因此它可能会影响到Redux store中其他部分的状态,从而导致后续的状态更新出现错误。正确的做法应该是返回一个新的state对象,而不是直接修改state对象,代码应该是这样的:
function reducer(state, action) {
return {
...state,
count: state.count + 1
};
}
在这种方式下,reducer函数成为了纯函数,它们的行为只依赖于它们的输入参数,即state和action,而不依赖于外部的状态。因此,这种方式保证了Redux store的状态的可预测性和可控性。
// 定义reducer函数: 纯函数
// 两个参数:
// 参数一: store中目前保存的state
// 参数二: 本次需要更新的action(dispatch传入的action)
// 返回值: 它的返回值会作为store之后存储的state
function reducer(state = initialState, action) {
switch(action.type) {
case "change_name":
return { ...state, name: action.name }
case "add_number":
return { ...state, counter: state.counter + action.num }
default:
return state
}
}
- 可以在派发
action之前,监听store的变化:
const unsubscribe = store.subscribe(() => {
console.log("订阅数据的变化:", store.getState())
})
// 修改store中的数据: 必须action
store.dispatch({ type: "change_name", name: "kobe" })
store.dispatch({ type: "change_name", name: "lilei" })
unsubscribe()//取消订阅
Redux结构划分,每个文件是什么作用?
redux代码优化:
1.将派发的action生成过程放到一个actionCreators函数中
2.将定义的所有actionCreators的函数, 放到一个独立的文件中: actionCreators.js
3.actionCreators和reducer函数中使用字符串常量是一致的, 所以将常量抽取到一个独立constants的文件中
4.将reducer和默认值(initialState)放到一个独立的reducer.js文件中, 而不是在index.js
- store -- index.js
const { createStore } = require("redux")
const reducer = require("./reducer.js")
// 创建的store
const store = createStore(reducer)
module.exports = store
- reducer -- reducer.js
const { ADD_NUMBER, CHANGE_NAME } = require("./constants")
// 初始化的数据
const initialState = {
name: "kobe",
counter: 100
}
function reducer(state = initialState, action) {
switch(action.type) {
case CHANGE_NAME:
return { ...state, name: action.name }
case ADD_NUMBER:
return { ...state, counter: state.counter + action.num }
default:
return state
}
}
module.exports = reducer
- action -- actionCreators.js
const { ADD_NUMBER, CHANGE_NAME } = require("./constants")
const changeNameAction = (name) => ({
type: CHANGE_NAME,
name
})
const addNumberAction = (num) => ({
type: ADD_NUMBER,
num
})
module.exports = {
changeNameAction,
addNumberAction
}
- constants -- constants.js
const ADD_NUMBER = "add_number"
const CHANGE_NAME = "change_name"
module.exports = {
ADD_NUMBER,
CHANGE_NAME
}
Redux使用流程
Redux官方图
redux如何和react结合在一起?如何共享数据,如何进行action操作?
- redux和react结合
- 核心代码主要是两个:
- 在
componentDidMount中定义数据的变化,当数据发生变化时重新设置 counter; - 在发生点击事件时,调用
store的dispatch来派发对应的action;
- 在
- 核心代码主要是两个:
export class Home extends PureComponent {
constructor() {
super()
this.state = {
// 初始化状态
counter: store.getState().counter, // 初始化为 store 中的 counter 状态
message: "Hello World", // 一些其他状态
friends: [
{id: 111, name: "why"},
{id: 112, name: "kobe"},
{id: 113, name: "james"},
]
}
}
componentDidMount() {
// 组件挂载后监听 store 中状态变化,更新组件状态
store.subscribe(() => {
const state = store.getState() // 获取最新的状态
this.setState({ counter: state.counter }) // 更新 counter 状态
})
}
addNumber(num) {
// 点击按钮后触发的事件,更新 store 中的状态
store.dispatch(addNumberAction(num))
}
render() {
const { counter } = this.state // 获取当前组件的状态
return (
<div>
<h2>Home Counter: {counter}</h2> // 渲染状态,显示当前 counter 值
<div>
<button onClick={e => this.addNumber(1)}>+1</button> // 点击按钮更新 store 中的状态
<button onClick={e => this.addNumber(5)}>+5</button>
<button onClick={e => this.addNumber(8)}>+8</button>
</div>
</div>
)
}
- react-redux使用 - connect、Provider这些帮助我们完成连接redux、react
-
Provider<React.StrictMode>:这是一个在开发模式下运行时的辅助工具,它可以帮助你发现应用中潜在的问题。它会执行额外的检查并警告一些不推荐的用法。在生产环境中,它不会有任何影响。在这个例子中,它包裹了其他组件,以启用严格模式。<Provider store={store}>:这是一个 React Redux 库提供的组件,它允许将 Redux store 中的数据传递给组件树中的所有组件。store是一个包含应用程序状态的 Redux store 对象,它通过这个组件被传递给了应用程序。这样,所有的组件都可以通过连接到 Redux store 来访问和更新数据。<App />:这是你的应用程序的根组件。它可能包含其他组件,形成一个组件树。在这个例子中,根组件被包裹在<Provider>中,这样它的子组件就可以通过连接到 Redux store 来获取状态和派发操作。
当调用
root.render()并传递上述 JSX 元素作为参数时,React 将会把这个元素及其子组件渲染到根节点中,从而启动你的应用程序。
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
connect
connect()//返回值是一个高阶组件
function mapStateToProps(state) {
return {
counter: state.counter
}
}
function fn2(dispatch) {
return {
addNumber(num) {
dispatch(addNumberAction(num))
},
subNumber(num) {
dispatch(subNumberAction(num))
}
}
}
// mapStateToProps 函数将 Redux store 中的状态映射到组件的 props 上
const mapStateToProps = (state) => ({
counter: state.counter, anners prop recommends: state.recommends // 将 state 中的 recommends 属性映射为组件的 recommends prop })// 将 state 中的 counter 属性映射为组件的 counter prop
banners: state.banners, // 将 state 中的 banners 属性映射为组件的 banners prop
recommends: state.recommends // 将 state 中的 recommends 属性映射为组件的 recommends prop
})
// mapDispatchToProps 函数将 dispatch 方法映射到组件的 props 上
const mapDispatchToProps = (dispatch) => ({
addNumber(num) {
dispatch(addNumberAction(num)) // 在组件中调用 addNumber 方法时,将触发 addNumberAction 并将其派发到 Redux store
},
subNumber(num) {
dispatch(subNumberAction(num)) // 在组件中调用 subNumber 方法时,将触发 subNumberAction 并将其派发到 Redux store
}
})
// 使用 connect 高阶函数将 mapStateToProps 和 mapDispatchToProps 的结果与 About 组件连接起来
export default connect(mapStateToProps, mapDispatchToProps)(About)
上述代码段展示了使用 react-redux 库中的 connect 函数将 Redux 的状态和操作映射到组件的过程。mapStateToProps 函数将 Redux store 中的状态映射为组件的 props,而 mapDispatchToProps 函数将 dispatch 方法映射为组件的 props,以便在组件中调用相应的操作时触发 Redux 的 action。最后,通过将 mapStateToProps 和 mapDispatchToProps 的结果与 About 组件连接起来,我们将得到一个新的通过 Redux 管理状态的 About 组件。
直接在props中解构或者使用
calcNumber(num, isAdd) {
if (isAdd) {
console.log("加", num)
this.props.addNumber(num)
} else {
console.log("减", num)
this.props.subNumber(num)
}
}
render() {
const { counter, banners, recommends } = this.props
}