一、redux的介绍
- Redux是JavaScript状态容器, 提供可与策划的状态管理。相当于vue的vuex, 可以跨组件、跨页面推送数据。应用场景如:购物车、会员登录等功能模块。
- Redux由Flux演变而来, 避开了Flux的复杂性. 不管你有没有使用过他们, 只需几分钟就能上手Redux。
- Redux的基本思想是整个应用的state保持在一个单一的store中。store就是一个简单的Javascript对象,而改变应用state的唯一方式在应用中触发actions,然后为这些actions编写reducers来修改state。整个state转化是在reducers中完成,并且不应该有任何副作用。
二、redux的安装
npm install redux --save
npm install react-redux --save
三、 redux的使用案例
3.1 先构建一个redux的store
## 我们要建立一个Count计数组件, 在pages/index页面中引入改组件, 然后在项目的index.js入口文件中维护好redux的store关系
## 1. Count 组件
import React from 'react'
class Index extends React.Component {
render() {
return (
<div>
子组件计数器: 0
</div>
)
}
}
export default Index
## 2. pages/index页面
import React from 'react';
import Count from '../../components/Count'
import {connect} from 'react-redux'
class Index extends React.Component {
constructor() {
super();
this.state = {}
this.num = 0;
}
// 1. 添加商品数量, 触发actions
incCount() {
this.props.dispatch({type: 'INC', data: {count: ++this.num}})
}
render() {
return (
<div>
<Count />
计数器: <button type={'button'}>-</button> {this.props.state.count} <button type={'button'} onClick={this.incCount.bind(this)}>+</button>
</div>
)
}
}
export default connect(state => {
return {
state: state
}
})(Index);
3. index入口页面
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore} from 'redux'
import {Provider} from 'react-redux'
import './assets/css/common/public.css';
import RouterComponent from './router';
import * as serviceWorker from './serviceWorker';
// 2. 商品装车
function counterReducer(state ={count: 10},action) {
console.log(action)
return state
}
// 3. 存入仓库
let store = createStore(counterReducer)
function App(){
return (
<React.Fragment>
<Provider store={store}>
<RouterComponent></RouterComponent>
</Provider>
</React.Fragment>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
serviceWorker.unregister();
备注: 上述代码主要是在入口文件中, 创建了reduce的store, 并把store通过Provider组件传递给下面的路由; 在pages/index页面中通过connent引入store, 同时本页面写了个相当于vuex中的dispatch,自增count的方法.这样就初始化好了redux的环境, 效果如下
3.2 初始化环境好了, 我们就开始通过redux实现count的自增了
只需要在入口的index未文中处理好dispatch传过来的值的逻辑就好了
// 2. 商品装车
function counterReducer(state ={count: 0},action) {
console.log(action)
if(action.data && action.data.count) {
// 引用对象, 使用es6的assign浅拷贝, 实现数据的实时更新
state = Object.assign({},state,action.data)
}
return state
}
3.3 接下来需要写递减方法, 在Count子组件中共享reducer中的count变量
## 1. 首先在pages/index 页面中写一个自减方法, 并绑定事件.
// 1.2. 减少商品数量, 触发actions
decCount() {
this.props.dispatch({type: 'DEC', data: {count: --this.num}})
}
<button type={'button'} onClick={this.decCount.bind(this)}>
## 2. index入口文件中区分dispatch过来的INC, 和DEC事件
// 2. 商品装车
function counterReducer(state ={count: 0},action) {
switch (action.type) {
case 'INC':
// 引用对象, 使用es6的assign浅拷贝, 实现数据的实时更新
return Object.assign({},state,action.data)
break;
case 'DEC':
// ES6中的浅拷贝
return {...state,...action.data}
break;
default:
return state
break;
}
}
## 3. 最后在Count组件中也通过connect形式去那ruduce中的count变量
import React from 'react'
import {connect} from 'react-redux'
class Index extends React.Component {
render() {
return (
<div>
子组件计数器: {this.props.state.count}
</div>
)
}
}
// state => ({state}) es6 返回state
export default connect(state => ({state}))(Index)
效果如下:
3.4 如何在不同业务中使用redux呢? 使用combineReducers方法
// 1. 新建一个简单的会员登录页面
import React from 'react';
import {connect} from 'react-redux'
class Index extends React.Component {
constructor(){
super();
this.state={
username:"",
password:""
}
}
doLogin(){
if(this.state.username.match(/^\s*$/)){
alert("请输入用户名");
return;
}
if(this.state.password.match(/^\s*$/)){
alert("请输入密码");
return
}
this.props.dispatch({type: 'LOGIN', data: {username: this.state.username, isLogin: true}})
this.props.history.go(-1);
}
componentDidMount(){
// console.log(this.props);
if(this.props.location.state){
console.log("从"+this.props.location.state.from.pathname+"页面跳转过来");
}
}
render() {
return (
<div>
用户名:<input type="text" placeholder="请输入用户名" onChange={(e)=>{this.setState({username:e.target.value})}}/><br/>
密码:<input type="text" placeholder="请输入密码" onChange={(e)=>{this.setState({password:e.target.value})}} /><br/>
<button type="button" onClick={this.doLogin.bind(this)}>登录</button>
</div>
)
}
}
export default connect()(Index);
// 2. 在index.js的入口文件中写好响应的reducer方法
// 默认的登录state
let defaultLoginState = {
username: localStorage['username'] ? localStorage['username'] : '' ,
isLogin: localStorage['isLogin'] ? Boolean(localStorage['isLogin']) : false
}
// 把登录的信息放入redux中
function loginReducer(state =defaultLoginState, action) {
switch (action.type) {
case 'LOGIN':
console.log(action)
// 刷新后reduce中的数据会丢失, 一般配合本地存储使用
localStorage['username']=action.data.username;
localStorage['isLogin']=action.data.isLogin;
return {...state,...action.data}
default:
return state
}
}
// 3. 使用combineReducers方法, 合并两个reducer方法, 相当于vuex中的分模块管理
let store = createStore(combineReducers({
counter: counterReducer,
login: loginReducer
}))
// 4. 修改之前使用的counter store
{this.props.state.count} 变成了 {this.props.state.counter.count}
效果如下:
3.5 使用redux模拟登录退出业务流程
// 1. 写一个用户中心退出页面
import React from 'react';
import {connect} from 'react-redux'
class Index extends React.Component {
constructor(props){
super(props);
this.state={};
}
componentDidMount(){
}
outLogin(){
this.props.dispatch({type:'OUT_LOGIN'})
this.props.history.replace("/login");
}
render() {
return (
<div>
欢迎{this.props.state.user.username}回来!<br/>
<button type="button" onClick={this.outLogin.bind(this)}>安全退出</button>
</div>
)
}
}
export default connect(state => ({state}))(Index);
// 2. 在index.js入口文件中写退出的reducers方法
case 'OUT_LOGIN':
localStorage.clear();
return Object.assign({},state,{username: '',isLogin: false})
// 3. 从react官网中copy一个router-auth的js, 相当于vue中路由守卫
import React from 'react';
import {Route,Redirect} from 'react-router-dom';
export function AuthRoute({ component:Component, ...rest }) {
return (
<Route {...rest} render={props =>
Boolean(localStorage['isLogin']) ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
}
// 4. 把router-auth 加入到用户中心页面, 这样用户退出后, 再进用户中心页面就会重定向到登录页面
<AuthRoute path="/user" component={UserPage}></AuthRoute>
以上代码就把用户登录退出的业务流程写好了(当然实际中会去调Api):
四、使用redux modules 分模块管理数据
我们发现在index.js入口文件写很多redux的业务, js会特别的冗余, 这时就需要分模块封装。
- 构建store module目录
# 1. reducers中
index.js
import {createStore,combineReducers} from 'redux'
import counterReducer from './count'
import loginReducer from './user'
// 3. 存入仓库
let store = createStore(combineReducers({
counter: counterReducer,
user: loginReducer
}))
export default store
user.js
// 默认的登录state
let defaultLoginState = {
username: localStorage['username'] ? localStorage['username'] : '' ,
isLogin: localStorage['isLogin'] ? Boolean(localStorage['isLogin']) : false
}
// 把登录的信息放入redux中
function loginReducer(state =defaultLoginState, action) {
switch (action.type) {
case 'LOGIN':
console.log(action)
localStorage['username']=action.data.username;
localStorage['isLogin']=action.data.isLogin;
return {...state,...action.data}
case 'OUT_LOGIN':
localStorage.clear();
return Object.assign({},state,{username: '',isLogin: false})
default:
return state
}
}
export default loginReducer
count.js
// 2. 商品装车
function counterReducer(state ={count: 0},action) {
switch (action.type) {
case 'INC':
// 引用对象, 使用es6的assign浅拷贝, 实现数据的实时更新
return Object.assign({},state,action.data)
case 'DEC':
// ES6中的浅拷贝
return {...state,...action.data}
default:
return state
}
}
export default counterReducer
2. acitions 中
index.js
import * as counter from './count'
import * as user from './user'
export default {
counter,
user
}
user.js
// 登录方法
export function login(data) {
return {type: 'LOGIN', data}
}
// 退出方法
export function logout() {
return {type:'OUT_LOGIN'}
}
count.js
// 自增方法
export function incCount(data) {
return {type: 'INC', data: data}
}
// 自减方法
export function decCount(data) {
return {type: 'DEC', data: data}
}
最后再改一下调用reducers和actions的地方就好了
index.js入口文件只需要导入store就好了, 不需要去处理具体业务
import store from './store/reducers/index'
<Provider store={store}>
<RouterComponent></RouterComponent>
</Provider>
// 其他使用到actions的页面, 只需要传数据就好了, 不需要指定type是什么
计算页
// 1.1 添加商品数量, 触发actions
incCount() {
// this.props.dispatch({type: 'INC', data: {count: ++this.num}})
this.props.dispatch(actions.counter.incCount({count: ++this.num}))
}
// 1.2. 减少商品数量, 触发actions
decCount() {
// this.props.dispatch({type: 'DEC', data: {count: --this.num}})
this.props.dispatch(actions.counter.decCount({count: --this.num}))
}
登录页
// this.props.dispatch({type: 'LOGIN', data: {username: this.state.username, isLogin: true}})
this.props.dispatch(actions.user.login( {username: this.state.username, isLogin: true}))
用户中心页
// this.props.dispatch({type:'OUT_LOGIN'})
this.props.dispatch(actions.user.logout())
结果还是和上面图一样的, module改造成功