Redux 是一个流行的 JavaScript 状态管理库,它主要用于管理复杂的应用状态。以下是一些 Redux 的主要应用场景:
大型应用: 对于大型的、有许多状态需要管理的应用,Redux 可以提供一个集中的地方来存储和管理这些状态,使得状态管理变得更加可预测和容易追踪。
状态共享: 当多个组件需要共享同一份状态,或者某个状态需要在组件树的多个深层组件之间传递时,Redux 可以提供一个方便的解决方案。
复杂的状态更新逻辑: 如果你的应用有一些复杂的状态更新逻辑,例如状态的更新依赖于其他状态,或者状态的更新需要异步操作,Redux 可以帮助你将这些逻辑集中管理。
跨浏览器会话的状态持久化: Redux 配合其他库(例如 redux-persist)可以方便地实现状态的持久化,例如将状态保存到本地存储,使得即使在浏览器刷新后,应用状态也能保持不变。
开发工具: Redux 提供了强大的开发工具,如 Redux DevTools,可以非常方便地跟踪、理解和调试状态更新的过程。
Redux 是一个用于管理 JavaScript 应用状态的库。它借鉴了 Flux 架构和函数式编程的一些理念,采用了严格的单向数据流,让状态变化变得可预测和易于理解。Redux 的设计思想主要有以下几点:
单一数据源: Redux 应用只有一个单一的全局状态树(store),所有的状态都存储在这里。这让状态的访问和修改变得非常直观,并且也便于调试和持久化。
状态是只读的: 在 Redux 中,你不能直接修改状态,而只能通过派发(dispatch)一个动作(action)来描述你想要如何改变状态。这样做可以确保所有的状态变化都被集中处理,并且可以按照一定的顺序执行。
纯函数修改状态: 在 Redux 中,所有的状态变化都由纯函数(reducer)处理。reducer 函数接收当前的状态和一个动作作为参数,并返回新的状态。因为 reducer 是纯函数,所以它们没有副作用,也不依赖外部的变量或状态,这让测试变得非常简单。
Redux 的设计思想使得状态管理变得清晰和可控。通过限制如何更新状态,Redux 为复杂应用提供了结构和数据一致性。同时,由于所有的状态更新都通过动作触发,因此可以很容易地实现如撤销/重做这样的功能,或者利用 Redux DevTools 进行时间旅行调试。
cra zhufengredux
cd zhufengredux
npm install redux --save
public\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<title>React App</title>
</head>
<body>
<div id="root"></div>
<div id="counter">
<p id="counter-value">0</p>
<button id="add-btn">+</button>
<button id="minus-btn">-</button>
</div>
</body>
</html>
src\index.js
import { createStore} from './redux';
let counterValue = document.getElementById('counter-value');
let addBtn = document.getElementById('add-btn');
let minusBtn = document.getElementById('minus-btn');
const ADD = 'ADD';
const MINUS = 'MINUS';
let initState = { number: 0 };
const reducer = (state = initState, action) => {
switch (action.type) {
case ADD:
return { number: state.number + 1 };
case MINUS:
return { number: state.number - 1 };
default:
return state;
}
}
let store = createStore(reducer);
function render() {
counterValue.innerHTML = store.getState().number + '';
}
store.subscribe(render);
render();
addBtn.addEventListener('click', function () {
store.dispatch({ type: ADD });
});
minusBtn.addEventListener('click', function () {
store.dispatch({ type: MINUS });
});
src\index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import Counter1 from './components/Counter1';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Counter1 />
);
src\components\Counter1.js
import React, { Component } from 'react';
import { legacy_createStore as createStore} from 'redux';
const ADD = 'ADD';
const MINUS = 'MINUS';
const reducer = (state = initState, action) => {
switch (action.type) {
case ADD:
return { number: state.number + 1 };
case MINUS:
return { number: state.number - 1 };
default:
return state;
}
}
let initState = { number: 0 };
const store = createStore(reducer, initState);
export default class Counter extends Component {
unsubscribe;
constructor(props) {
super(props);
this.state = { number: 0 };
}
componentDidMount() {
this.unsubscribe = store.subscribe(() => this.setState({ number: store.getState().number }));
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
return (
<div>
<p>{this.state.number}</p>
<button onClick={() => store.dispatch({ type: 'ADD' })}>+</button>
<button onClick={() => store.dispatch({ type: 'MINUS' })}>-</button>
<button onClick={
() => {
setTimeout(() => {
store.dispatch({ type: 'ADD' });
}, 1000);
}
}>1秒后加1</button>
</div>
)
}
}
bindActionCreators
是一个 Redux 的函数,用于把 action creators 绑定到 dispatch 函数,让你可以直接调用它们,而不是每次都要调用 dispatch(actionCreator())
。
这个函数特别有用,当你需要把 action creators 传给一个组件,然后让它能够直接调用 action 创建函数来触发 action,而无需直接调用 dispatch 函数。
在 react-redux
库的 connect
函数中,你也可以直接传入一个包含了多个 action creators 的对象,connect
函数会自动帮你完成绑定操作:
src\components\Counter1.js
import React, { Component } from 'react';
+import { legacy_createStore as createStore,bindActionCreators} from 'redux';
const ADD = 'ADD';
const MINUS = 'MINUS';
const reducer = (state = initState, action) => {
switch (action.type) {
case ADD:
return { number: state.number + 1 };
case MINUS:
return { number: state.number - 1 };
default:
return state;
}
}
let initState = { number: 0 };
const store = createStore(reducer, initState);
+function add() {
+ return { type: 'ADD' };
+}
+function minus() {
+ return { type: 'MINUS' };
+}
+const actions = { add, minus };
+const boundActions = bindActionCreators(actions, store.dispatch);
export default class Counter extends Component {
unsubscribe;
constructor(props) {
super(props);
this.state = { number: 0 };
}
componentDidMount() {
this.unsubscribe = store.subscribe(() => this.setState({ number: store.getState().number }));
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
return (
<div>
<p>{this.state.number}</p>
+ <button onClick={boundActions.add}>+</button>
+ <button onClick={boundActions.minus}>-</button>
<button onClick={
() => {
+ setTimeout(boundActions.add, 1000);
}
}>1秒后加1</button>
</div>
)
}
}
combineReducers
接受一个对象作为参数,该对象的每个键值代表不同的状态切片,并对应一个处理该状态切片的 reducer 函数。返回的 reducer 函数在调用时会将传入的 state 对象切分成与之匹配的部分,并将每个部分传递给相应的 reducer 函数。
通过使用 combineReducers
,你可以更容易地将逻辑划分为模块化的方式,每个模块都有自己的 reducer 和相关状态,这有助于提高代码的可读性和可维护性。
src\index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
+import Counter1 from './components/Counter1';
+import Counter2 from './components/Counter2';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
+ <div>
+ <Counter1/>
+ <Counter2/>
+ </div>
);
src\store\action-types.js
export const ADD1 = 'ADD1';
export const MINUS1 = 'MINUS1';
export const ADD2 = 'ADD2';
export const MINUS2 = 'MINUS2';
src\store\reducers\counter1.js
import * as types from '../action-types';
let initialState = { number: 0 }
export default function (state=initialState, action) {
switch (action.type) {
case types.ADD1:
return { number: state.number + 1 };
case types.MINUS1:
return { number: state.number - 1 };
default:
return state;
}
}
src\store\reducers\counter2.js
import * as types from '../action-types';
let initialState = { number: 0 };
export default function (state=initialState, action) {
switch (action.type) {
case types.ADD2:
return { number: state.number + 1 };
case types.MINUS2:
return { number: state.number - 1 };
default:
return state;
}
}
src\store\reducers\index.js
import { combineReducers} from 'redux';
import counter1 from './counter1';
import counter2 from './counter2';
let rootReducer = combineReducers({
counter1,
counter2
});
export default rootReducer;
src\store\index.js
import { legacy_createStore as createStore } from 'redux';
import reducer from './reducers';
const store = createStore(reducer, { counter1: { number: 0 }, counter2: { number: 0 } });
export default store;
src\store\actions\counter1.js
import * as types from '../action-types';
const actions = {
add1() {
return { type: types.ADD1 };
},
minus1() {
return { type: types.MINUS1 };
}
}
export default actions;
src\store\actions\counter2.js
import * as types from '../action-types';
const actions = {
add2() {
return { type: types.ADD2 };
},
minus2() {
return { type: types.MINUS2 };
}
}
export default actions;
src\components\Counter1.js
import React, { Component } from 'react';
+import { bindActionCreators } from 'redux';
+import actions from '../store/actions/counter1';
+import store from '../store';
+const boundActions = bindActionCreators(actions, store.dispatch);
export default class Counter extends Component {
unsubscribe;
constructor(props) {
super(props);
this.state = { number: 0 };
}
componentDidMount() {
+ this.unsubscribe = store.subscribe(() => this.setState({ number: store.getState().counter1.number }));
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
return (
<div>
+ <p>{this.state.number}</p>
+ <button onClick={boundActions.add1}>+</button>
+ <button onClick={boundActions.minus1}>-</button>
</div>
)
}
}
src\components\Counter2.js
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import actions from '../store/actions/counter2';
import store from '../store';
const boundActions = bindActionCreators(actions, store.dispatch);
export default class Counter extends Component {
unsubscribe;
constructor(props) {
super(props);
this.state = { number: 0 };
}
componentDidMount() {
this.unsubscribe = store.subscribe(() => this.setState({ number: store.getState().counter2.number }));
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
return (
<div>
<p>{this.state.number}</p>
<button onClick={boundActions.add2}>+</button>
<button onClick={boundActions.minus2}>-</button>
</div>
)
}
}
react-redux
是一个库,提供了两个主要的接口 Provider
和 connect
,用于将 React 应用和 Redux store 连接起来。它使得我们能够在 React 组件中访问和使用 Redux store 中的数据。
Provider:它是一个 React 组件,负责提供 Redux store 给子组件。我们通常在应用的最外层使用这个组件,并且只使用一次。这使得我们的 Redux store 变成了应用的 context 的一部分,可以被应用中的其他组件访问。
connect:这是 react-redux
的一个函数,用于连接 React 组件和 Redux store。connect
函数接受四个参数(都是可选的):mapStateToProps
、mapDispatchToProps
、mergeProps
和 options
。它返回一个新的函数,这个新函数需要接受一个 React 组件作为参数,并返回一个新的已连接 Redux store 的 React 组件。
react-redux
提供了一种简洁、易于理解的方式来集成 React 和 Redux,使得 React 组件可以更方便地从 Redux store 中读取数据以及派发 actions。
src\index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import Counter1 from './components/Counter1';
import Counter2 from './components/Counter2';
+import store from './store';
+import { Provider } from 'react-redux';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
+ <Provider store={store}>
<Counter1/>
<Counter2/>
+ </Provider>
);
src\components\Counter1.js
import React, { Component } from 'react';
+import actions from '../store/actions/counter1';
+import { connect } from '../react-redux';
class Counter1 extends Component {
render() {
+ let { number, add1, minus1 } = this.props;
return (
<div>
+ <p>{number}</p>
+ <button onClick={add1}>+</button>
+ <button onClick={minus1}>-</button>
</div>
)
}
}
let mapStateToProps = (state) => state.counter1;
export default connect(
mapStateToProps,
actions
)(Counter1)
src\components\Counter2.js
import React from 'react';
+import { useSelector,useDispatch} from 'react-redux';
import actions from '../store/actions/counter2';
const Counter2 = () => {
+ const counter2 = useSelector(state => state.counter2);
+ const dispatch = useDispatch();
return (
<div>
<p>{counter2.number}</p>
+ <button onClick={()=>dispatch(actions.add2())}>+</button>
+ <button onClick={()=>dispatch(actions.minus2())}>-</button>
</div>
)
}
export default Counter2;