redux
1、redux介绍
状态管理,类似于vue中的vueX。
-
Redux是为javascript应用程序提供一个可预测(根据一个固定的输入,必然会得到一个固定的结果)的状态容器。可以运行于服务端,客户端,原生应用,从Flux演变而来;
-
集中的管理react中多个组件的状态;
-
redux是专门作状态管理的js库,并不是react的插件库,也可以用在其他js框架中,例如vue,但是基本用在react中
2、redux成员
成员 | 作用 | 类型 |
---|---|---|
createStore(reducer,state) | 创建store实例(state:仓库存放的数据;reducer:对数据的操作) | 函数 |
combineReducers | 合并多个reducer | 函数 |
applyMiddleware | 安装中间件,改装增强redux | 函数 |
store成员
成员 | 作用 | 类型 |
---|---|---|
subscribe(回调函数) | 订阅state变化 | 函数 |
dispatch(action) | 发送action 给 reducer | 函数 |
getState() | 获取一次state的值 | 函数 |
replaceReducer | 一般在 Webpack Code-Splitting 按需加载的时候用 | 函数 |
数据流动
component(views) | action | reducer | state | component(views) |
---|---|---|---|---|
展示state | 转发的动作,异步业务 | 同步业务处理逻辑, 修改state,并且返回新的state | 状态收集 | |
store.dispatch—》 | -------------》 | 《–subscribe | ||
《–getState |
示例代码:
1)、组件从store中获取state(数据)):
//创建一个store,
//创建store需要reducer和state。即就是:创建仓库时,需要说清楚仓库中存储的数据(state),以及对数据的操作(reducer)
./src/plugin/redux.js
import { createStore} from "redux";
import state from "../store/state";
import reducer from "../store/reducer";
//1、 创建仓库,并且说清楚,仓库里存储的数据,以及,对仓库数据的操作
// createStore :创建仓库的
// state:仓库里存储的数据
// reducer:对仓库数据的操作
let store = createStore(reducer,state);
export default store;
//2、创建state
//state就是仓促存储的数据(对象)
./src/store/state.js
export default {
count:0
}
//3、创建reducer
//对仓库数据进行操作的函数(函数)。
//要求:传入旧的state,返回新的state。
./src/store/reducer.js
// reducer要求是个纯函数(在函数内部不能修改函数的参数(输入),要有返回值),它的功能是:传入旧的state,根据action对state做操作,返回新的state。
// 参数:
// state:原始的state
// action:要做的事情,动作的类型
// 返回值:必须要有有,是新的state(修改后的state)。getState()函数会调用reducer
// 因为是纯函数,所以,在函数内部,不能修改state和action。
let reducer = (state,action)=>{
if(action.type){
//对state的操作
}
switch(action.type){
case 添加:……………………return 新的state;break;
case 删除:……………………return 新的state;break;
}
return state;//返回新的state
}
export default reducer;
//组件
./src/App.js
import store from "./plugins/redux";
class App extends React.Component {
state = {
}
render = () => (
<div>
<div className="App">
<p>{store.getState().count}</p>
</div>
</div>
)
}
2)、通过组件修改store中的state(数据)):
注意:修改store中的state(数据)后,还需要把数据响应到组件上,就需要使用 subscribe。
修改reducer
./src/store/reducer.js
let reducer = (state,action)=>{
let {type,payload} = action;
switch(type){
case "INCREMENT":{
console.log("加一");
return {
...state,
count:state.count+payload
}
}
default:return state;
}
}
//修改App组件的代码
./src/App.js
class App extends React.Component {
constructor(props){
super(props);
this.state = {
count:store.getState().count
}
store.subscribe(()=>{
this.setState({
count:store.getState().count
});
})
}
inc(){
store.dispatch({type:"INCREMENT",payload:10});
}
render = () => (
<div>
<div className="App">
<p>{this.state.count}</p>
<input type="button" value="增加" onClick={()=>{this.inc()}} />
</div>
</div>
)
}
3、流程总结
安装:
npm i --save redux
import {createStore} from 'redux'
//一、创建reducer
//reducer:对仓库(数据)的操作
//参数:
// state:传入的旧数据(原始数据)
// action:对数据的操作
//返回值:操作后的新的数据
const reducer = (state,action)=>{
let {type,payload}=action
swtich (type){
case XXXXX :{
//数据的逻辑处理
return {
...state,
属性名:新的值
}
}
default:
return state
}
}
//二、创建state对象
// 仓库里的数据
export default {
count:0
}
//三、创建store对象(仓库)
//使用createStore(对仓库的操作,仓库的数据)
store = createStore(reducer,state)
export default store;
//四、在组件内部使用仓库(如:获取仓库的数据,修改仓库的数据,添加,删除)
import store from '...'
store.getState() //获取状态,执行一次
store.dispatch({type:xxx,payload:ooo}) //发送action给reducer type是必传参数
store.subscribe(回调) //订阅 state 更新state时触发
4、提取并定义 Action Creators
./src/store/actionCreators.js
export const increment = payload =>({
type:"INCREMENT",
payload
})
export const decrement = payload =>({
type:"DECREMENT",
payload
})
App.js组件
./src/App.js
import { increment } from "./store/actionCreators";
store.dispatch(increment(10));
5、action里处理异步
需要安装中间件 redux-thunk ,redux-thunk可以增强dispatch的功能,让dispatch可以接受一个函数作为参数。
./src/plugins/redux.js
//安装中间件改装 redux
import {createStore,applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
let store = createStore(rootReducer,rootState,applyMiddleware(thunk));
./src/store/actionCreators.js
//处理异步
export let ADD =()=>((dispatch)=>{
axios({
url:"http://localhost:3000/inc",
})
.then(res=>{
dispatch({
type:"INCREMENT",
payload:res.data.num
})
})
})
App组件
./src/App.js
import { increment,decrement,ADD } from "./store/actionCreators";
store.dispatch(ADD());
6、combineReducers提取reducer
把一个大的reducer拆成若干个小的。注意:把大的state也分开,并且放在每个reducer。也就是说,每个reducer里面写上它要操作的数据,这样的话,在一个reducer里包含了,数据(state)和数据的操作(reducer)。
// ./src/plugins/myRedux.js
import {createStore,applyMiddleware,combineReducers} from 'redux'
import thunk from 'redux-thunk'
import count from "../store/reducers/count";
import todos from "../store/reducers/todos";
let rootReducer = combineReducers({
todos:todos,
count:count
});
//去掉了第二个参数state(因为state拆分到了每个reducer里了)
export default createStore(rootReducer,applyMiddleware(thunk));
// ./src/store/reducers/count.js
// 是当前reducer要操作的数据
let initCount = 8;
const count = (count=initCount,action)=>{
switch(action.type){
case "INCREMENT":{
return count+action.payload;
}
case "DECREMENT":{
return count-action.payload;
}
default: return count;
}
}
export default count;
// ./src/store/reducers/todos
let initState=[] //当前reducer所操作的数据,放在里自己的模块里。
const todos = (todos, action) => {
switch (action.type) {
case "ADD_TODO": {
return [
...todos,
{
id: action.id,
text: action.text,
completed: false
}
]
}
case "REMOVE_TODO": {
const { id } = action;
todos.map((item,index) => item.id ===id && todos.splice(index, 1));
return [...todos]
}
case "CHECK_TODO": {
const { id } = action;
todos.map((item,index) => item.id ===id && (todos[index].completed=!todos[index].completed));
return [...todos]
}
default:
return todos;
}
};
export default todos;
//删除掉state文件。
//组件里:
写法基本上不变
store.getState().todos
state数据不写在类内部订阅,可以写在主入口文件 订阅store数据的更新
let render = ()=>{
ReactDOM.render(
<App/>,
document.getElementById('root')
)
};
render();
store.subscribe(render);
7、react-redux
基于redux思想,专门为react使用redux而生,react-redux是连接redux和react组件的桥梁
redux里的问题:
1、组件中出现了大量的store对象
2、 在redux里,凡是使用state里数据的组件,必须加上 store.subscribe() 函数,否则,数据不是响应式的
8、react-redux的API
(react-redux仅有2个API)
-
组件:可以让组件拿到state(不需要使用传统的subscribe()来监听state重绘组件)
import {Provider} from "react-redux"; import store from './redux/store' ReactDOM.render(( <Provider store={store}> <App/> </Provider> ), document.getElementById('root'));
-
connect(): 链接 ,(返回值)是个高阶组件,用来链接react组件和redux(组件状态要从redux中获取)
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
功能:把store和react组件联系在一起。只要store发生了变化就会调用mapStateToProps方法。Connect方法就是个高阶组件。
参数1:mapStateToProps是个函数,
功能: 把仓库里的state合并到props里。给mapStateToProps函数传入所有state**,它返回指定的state数据(需要合并到组件props中的state)。返回的state与组件的 props 合并(联系的体现)。所以,当store发生变化时,mapStateToProps方法就会更新组件里的props,那么组件就更新了(因为props变了)。
参数:
state:所有的state
返回值:
经过处理后的state(组件里需要的state)。
示例代码:
const mapStateToProps = (state)=>{
return {
count:state.count
}
}
参数2:mapDispatchToProps函数
功能:
把dispatch和props联系起来。传入dispatch,返回绑定好的action方法。
更改数据必须要触发action,所以,mapDispatchToProps把 action 作为组件的props 进行绑定(联系的体现),要派发的函数名,可以是多个函数。mapDispatchToProps 就是用于建立组件跟store.dispatch的映射关系。
参数:
dispatch: 派发
ownProps:当前组件的props,即使用标签时,传入的props
返回值:
对象:表示所有dispatch的对象
示例代码:
import React from "react";
import './App.css';
import {ADD,REDUCE} from "./store/action";
import {connect} from "react-redux";
class App extends React.Component {
add(){
this.props.add();
}
reduce(){
this.props.reduce();
}
render = () => {
return (
<div className="App">
<p>{this.props.count}</p>
<input type="button" value=" 加 " onClick={()=>this.add()} />
<input type="button" value=" 减 " onClick={()=>this.reduce()} />
</div>
)
}
}
export default connect((state)=>{
return {
count:state.count
}
},dispatch=> ({
add: () => dispatch(ADD()),
reduce: () => dispatch(REDUCE())
}))(App);
react-redux的思路:
1)、用Provider包裹最顶层的组件,提供一个store属性。这样redux任何组件里都可以使用store了。
2)、使用connect()函数来链接react的组件和redux的store。记住:connect不能单独使用,必须要有Provider
9、最佳实现
安装:npm install --save react-redux
//1、主入口文件 index.js
import {Provider} from 'react-redux'
import store from './plugins/redux'
<Provider store={store}>
<App/>
</Provider>
//2、容器组件里:App组件
import {connect} from "react-redux";
class App extends React.Component {
add(){
//直接用props来调用dispatch,而不需要store
this.props.dispatch({
type:"INCREMENT",
payload:2
});
}
render = () => (
<div className="App">
<p>{this.props.count}</p> // 使用props可以直接拿到state里的数据,而不需要store
<input type="button" value=" 加 " onClick={()=>this.add()} />
</div>
)
}
//容器组件对外开放时,(把redux里的state转到props)
export default connect((state)=>{
return {
count :state.count
}
})(App);
在react-redux里,把组件进行拆分(容器组件和UI组件)
容器组件:处理业务逻辑,有状态(在redux里存放)组件,
UI组件:只做展示,就是无状态组件