1、redux描述
Redux 是 JavaScript 状态容器,提供可预测化的状态管理, 学习网站
2、redux的基本使用
redux的三大原则: 单一数据源(只创建一个store) state是只读的 使用纯函数来执行修改
安装
npm i redux --save
初始化redux
import {createStore} from 'redux'
let initState = {
name: 'yfbill',
age: 20
}
const reducer = (state = initState , action) => {
switch(action.type) {
case 'ADD': return {...state, age: ++state.age}; // 这里的...state表示里面原有的数据不能变, age对数据进行覆盖
case 'MINUS': return {...state, age: --state.age};
case 'CHANGE_NAME': return {...state, name: action.target}
default: return {...state}
}
}
export default createStore(reducer) //初始化store实例
举例:
store有以下职责
-
维持应用的 state;
-
提供
getState()
方法获取 state; -
提供
dispatch(action)
方法更新 state; -
通过
subscribe(listener)
注册监听器; -
通过
subscribe(listener)
返回的函数注销监听器。
import React, {Component} from 'react'; import ReactDom from 'react-dom' import store from './store' class App extends Component { componentDidMount() { this.fn = store.subscribe(() => { //redux内部的观察者,当数据发生变化的时候就会触发对应的方法,该方法返回是取消监听的函数 this.setState({}) //数据刷新后实现页面刷新 }) } componentWillUnmount() { this.fn() //取消对应的事件监听 } render() { return <div> <h2>this is App info</h2> <h3>name---{store.getState().name}</h3> <h3>age---{store.getState().age}</h3> <button onClick={() => { store.dispatch({type: 'ADD'}) }}>age++</button> </div> } } ReactDom.render(<App/>, window.root)
redux层的定义(这个就是redux生成store实例的方法)
import {createStore} from 'redux' let initState = { name: 'yfbill', age: 20 } const reducer = (state = initState , action) => { switch(action.type) { case 'ADD': return {...state, age: ++state.age}; case 'MINUS': return {...state, age: --state.age}; case 'CHANGE_NAME': return {...state, name: action.target} default: return {...state} } } export default createStore(reducer) //接收两个参数,一个是函数, 一个是中间件
因此通常公司中用redux进行开发,会以四个文件进行存放
- actionCreators.ts 存放dispatch中的函数封装
- constants.ts 存放action type的常量
- index.ts 导出creatorStore的总出口
- reducer.ts 存放reducer与初始化的state数据
3、react-redux的使用
为了实现react中更好的使用redux,可以使用依赖,react-redux实现两者更好的融合, 这个是把react与redux连接的依赖
安装react-redux
npm install react-redux
构建store依赖第二点所列的四个文件内容依次如下:
actionCreators.ts (存放dispatch中的函数封装)
import { ADD_COUNTER, REDUCE_COUNTER } from './constants';
// 递增state中的counter
export const addStateCounter = (payload: number) => ({
type: ADD_COUNTER,
payload,
});
// 递减state中的counter
export const reduceStateCounter = (payload: number) => ({
type: REDUCE_COUNTER,
payload,
});
constants.ts (存放action type的常量)
export const ADD_COUNTER = 'add_counter';
export const REDUCE_COUNTER = 'reduce_counter';
reducer.ts (存放初始化的state与reducer函数)
import { Reducer } from 'redux';
import { ADD_COUNTER, REDUCE_COUNTER } from './constants';
export interface IState {
counter: number;
}
const initState: IState = {
counter: 0,
};
export interface IAction {
type: string;
[key: string]: any;
}
export const reducer: Reducer<IState, IAction> = (
state: IState = initState,
action: IAction,
): IState => {
switch (action.type) {
case ADD_COUNTER:
return { ...state, counter: state.counter + action.payload };
case REDUCE_COUNTER:
return { ...state, counter: state.counter - action.payload };
default:
return state;
}
};
index.ts (导出store)
import { createStore } from 'redux';
import { reducer } from './reducer';
export default createStore(reducer);
react-redux provider的使用
import { Provider } from 'react-redux';
import { PureComponent, ReactElement } from 'react';
import store from './store';
import Content from './components/content';
class App extends PureComponent {
public render(): ReactElement {
return (
<Provider store={store}>
<h1>this is app</h1>
<Content />
</Provider>
);
}
}
export default App;
这里外层包Provider 并且注入store,这样子组件就方便进行接收
react-redux connect的使用
import { Dispatch } from 'redux';
import { connect } from 'react-redux';
import { PureComponent, ReactElement } from 'react';
import { IState } from '../store/reducer';
import { addStateCounter, reduceStateCounter } from '../store/actionCreators';
interface IContentProps {
counter: number;
addCounter: (num: number) => void;
reduceCounter: (num: number) => void;
}
class Content extends PureComponent<IContentProps> {
public render(): ReactElement {
const { counter, addCounter, reduceCounter } = this.props;
return (
<div>
<div>
<span>counter: </span>
<span>{counter}</span>
</div>
<div>
<button onClick={() => addCounter(1)}>+1</button>
<button onClick={() => reduceCounter(1)}>-1</button>
</div>
</div>
);
}
}
// 这个是把state注入到props
const mapStateToProps = (state: IState) => {
return {
counter: state.counter,
};
};
// 这个是把方法注入的props
const mapDispatchToProps = (dispatch: Dispatch) => ({
addCounter: (num: number) => dispatch(addStateCounter(num)),
reduceCounter: (num: number) => dispatch(reduceStateCounter(num)),
});
export default connect(mapStateToProps, mapDispatchToProps)(Content);
4、多个reducer合并的场景
一个项目中,如果对模块进行划分,那么就会产生多个reducer,这个时候就需要对多个reducer进行合并
在第3点的基础上对代码进行调整,把目录结构调整成如下
添加counter中的index.ts进行模块的导出(把IState改成ICounterState),具体代码如下:
export * from './actionCreators';
export type { ICounterState } from './reducer';
export { reducer } from './reducer';
在store/index.ts中代码进行如下调整
import { createStore, combineReducers } from 'redux';
import { reducer as counterReducer, ICounterState } from './modules/counter';
export interface ICombineState {
counter: ICounterState;
}
const reducerCombine = combineReducers({
counter: counterReducer,
});
export default createStore(reducerCombine);
并且把 connect处的state暴露部份进行调整
...
// 这个是把state注入到props
const mapStateToProps = (state: ICombineState) => {
return {
counter: state.counter.counter,
};
};
// 这个是把方法注入的props
const mapDispatchToProps = (dispatch: Dispatch) => ({
addCounter: (num: number) => dispatch(addStateCounter(num)),
reduceCounter: (num: number) => dispatch(reduceStateCounter(num)),
});
export default connect(mapStateToProps, mapDispatchToProps)(Content);
5、 redux-thunk的使用
在react的日常开发中,通常会把请求服务写在redux中,但是在dispatch的时候,往往不接受函数,因此就需要使用中间伯redux-thunk来实现该功能(以下例子是对上面的例子进行改造)
在actionCreator中添加指令函数如下:
import { ThunkAction } from 'redux-thunk';
import { Dispatch, Action } from 'redux';
import { ADD_COUNTER, REDUCE_COUNTER, QUERY_USER_INFO } from './constants';
import { ICounterState } from './reducer';
...
const mockFunc = (): Promise<{ name: string; email: string }> => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
name: `even${Math.round(Math.random() * 10 + 1)}`,
email: '457491559@qq.com',
});
}, 3000);
});
};
export const queryUserInfo = (): ThunkAction<
void,
ICounterState,
unknown,
Action<string>
> => {
return async (dispatch: Dispatch, state: () => ICounterState) => {
const { name, email } = await mockFunc();
console.log(state());
dispatch({
type: QUERY_USER_INFO,
userInfo: { name, email },
});
};
};
在store出口处启用中间件注册
import { createStore, compose, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { reducer as counterReducer, ICounterState } from './modules/counter';
export interface ICombineState {
counter: ICounterState;
}
const reducerCombine = combineReducers({
counter: counterReducer,
});
export default createStore(
reducerCombine,
applyMiddleware(thunk)
);
这个时候就可以在调用层进行使用了
...
// 这个是把方法注入的props
const mapDispatchToProps = (dispatch: Dispatch<any>) => ({
addCounter: (num: number) => dispatch(addStateCounter(num)),
reduceCounter: (num: number) => dispatch(reduceStateCounter(num)),
queryUserInfo: () => dispatch(queryUserInfo()),
});
export default connect(mapStateToProps, mapDispatchToProps)(Content);
6、react插件的使用
为了方便开发者进行开发和调试,可以安装如下两个插件
- redux-devtools
- react-devtools
在使用redux-devtools的时候需要在redux中进行配置,具体配置如下:
import { createStore, compose, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { reducer as counterReducer, ICounterState } from './modules/counter';
// const composeEnhancers =
// (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ||
// compose;
// 以下配置是相当于redux-devtools中的trace的配置打开
const composeEnhancers =
(window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ trace: true }) ||
compose;
export interface ICombineState {
counter: ICounterState;
}
const reducerCombine = combineReducers({
counter: counterReducer,
});
export default createStore(
reducerCombine,
composeEnhancers(applyMiddleware(thunk)),
);