搞懂redux

redux

redux原理

Redux 是一种新型的前端“架构模式”(Flux 架构的一种变种),它不关注你到底用什么库,你可以把它应用到 React 和 Vue,甚至跟 jQuery 结合都没有问题。

Redux 的基本做法:用户发出 Action,Reducer 函数算出新的 State,View 重新渲染。

  • React和Redux

    事实上是两个独立的产品,项目中可以使用 React 而不使用Redux ,也可以使用 Redux 而不使用 React

  • 什么是React-redux

    就是把 Redux 这种架构模式和 React 结合起来的一个库,是 Redux 架构在 React 中的体现,利用这个库能够大大简化代码的书写

Redux设计和使用的三项基本原则

  1. store是必须是唯一的

  2. 只有store能改变自己的内容

    reducer 可以接受state,但是绝对不能修改state

  3. Reducer必须是一个纯函数

    纯函数指的是,给固定的输入,就一定会有固定的输出,而且不会有任何副作用

什么时候需要Redux

Redux作者:如果你不知道是否需要 Redux,那就是不需要它
只有遇到 React 实在解决不了的问题,你才需要 Redux

  • 某个组件的状态,需要共享 比如:用户头像信息
  • 某个状态需要在任何地方都可以拿到
  • 在一个组件中需要改变全局状态 比如:底部tabbal的显示和隐藏
  • 在一个组件中需要改变另一个组件的状态

redux和vuex的区别

redux与使用的框架无关系;如果那天不用了,代码注释或删除没问题的,组件不影响,store代码与组件时分离的;

vuex是用this.$store.state=xxx,通过组件实例获取,是耦合的关联的;

redux使用

redux使用步骤

  1. 引入redux
    import {createStore} from 'redux';
  1. 创建一个仓库
    const store = createStore(Reducer);
  1. 使用
    • 获取state
      • store.getState()
    • 修改state:唯一修改state的方式就是reducer
      • store.dispath(action)
    • 监听state
      • store.subscribe(fn)

redux核心

  • reducer

    • Reducer 必须是一个纯函数
    • 用于指定state修改逻辑
    • 它接受当前 state 和 action 作为参数,并根据state和action的值返回新的state
  • store

    • 常用方法
      • store.getState() 获取仓库最新state状态 对比:Vue: this.$store.state
      • store.dispatch() 触发reducer,并修改state
      • store.subscribe() 监听state修改 ,类似于观察者 可以监听多个
  • action

    格式:{ type:'xxx' }

  • state: 状态,每一个存放数据的地方,为什么叫状态?因为可以随便修改

    • state有个方法:store.getState() 获取最新状态,只要state有修改,getState就是获取最新的状态;
/**
 * redux核心概念
    * store
    * reducer   state的修改逻辑
      * 参数
        * state
        * action
    * action
        * 格式:{type:''}
*/

// 1. 引入reduxt
import {
  createStore
} from 'redux'

// 这个应用的初始状态,可以作为state的默认值
const initState = {
  currentUser: {},
  // 默认显示菜单
  showMenu: true,
}

// reducer 是一个函数,它接受 当前state和 收到的action作为参数,返回一个新的state
// dispatch({type:'currentUser',user})
// dispatch({type:'showMenu'})
const reducer = function (state = initState, action) {
  // console.log('reducer', state, action)
  switch (action.type) {
    case 'login':
      return {
        ...state,
        currentUser: action.user
      }
    case 'logout':
      return {
        ...state,
        currentUser:{}
      }
    case 'showmenu':
      return {
        ...state,
        showMenu: action.showMenu
      }
    default:
      return state
  }
}

// 2. 创建仓库
//实际应用中,Reducer 函数不用手动调用,store.dispatch方法会触发 Reducer 的自动执行。
//为此,Store 需要知道 Reducer 函数,做法就是在生成 Store 的时候,将 Reducer 传入createStore方法。
const store = createStore(reducer)
// 上面代码中,createStore接受 Reducer 作为参数,生成一个新的 Store
// 以后每当store.dispatch发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State。

export default store

// 3. 使用
// 获取:store.getState()
// 修改:store.dispatch(action) // action —— {type:'xxx'[,传递的参数]}
// 监听:store.subscribe(fn)  // fn 是一个函数 
// 可以监听多个
// store.subscribe(fn1)  // fn1 是一个函数 
// store.subscribe(fn2)  // fn2 是一个函数 

login组件:

import React from 'react'
import CryptoJS from 'crypto-js'
import request from '@/utils/request'

import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import userAction, { login } from '../store/actions/user.js'

// 引入 store 前提必须导出 store
import store from '../store' 
// 监听state修改
store.subscribe(function(){
  // 这个函数在state有修改时执行(调用dispatch时执行)
  const state  =  store.getState()
  console.log('state=', state)
})
import { Form, Input, Button, Checkbox } from 'antd';
const layout = {
  labelCol: { span: 4 },
  wrapperCol: { span: 16 },
};
const tailLayout = {
  wrapperCol: { offset: 4, span: 16 },
};

// 类组件
class Login extends React.Component {

  // 成功时
  onFinish = async ({ username, password, mdl }) => {
    password = CryptoJS.SHA256(password)
    password = CryptoJS.enc.Hex.stringify(password)
    const data = await request.get('user/login', {
      username,
      password,
      mdl
    })
    // console.log('Success:', data);
    if (data.status === 200) {
      this.props.history.push('/profile')
      // localStorage.setItem('currentUser', JSON.stringify(data.data))

      console.log("login.props=", this.props)
      // 触发reducer, 并修改state 把user:data.data 传到action中,后面在store的index文件中 , 传到reducer 是 action.user
      store.dispatch({type:'login},user:data.data)
  
      // 当使用高阶组件 mapDispatchToProps 写下面这种即可,就不用上面写法
      // this.props.login(data.data)
    }
  };

  // 失败时
  onFinishFailed = errorInfo => {
    console.log('Failed:', errorInfo);
  };

  // 校验
  rules = {
    username: [{ required: true, message: '用户名必填' }],
    password: [{ required: true, message: '密码必填' }],
  }

  render() {
    return <div>
      <div style={{ margin: 10, fontSize: 20 }}>用户登录</div>
      <Form
        {...layout}
        name="basic"
        initialValues={{ remember: true }}
        onFinish={this.onFinish}
        onFinishFailed={this.onFinishFailed}
      >
        <Form.Item
          label="用户名"
          name="username"
          rules={this.rules.username}
        >
          <Input />
        </Form.Item>

        <Form.Item
          label="密码"
          name="password"
          rules={this.rules.password}
        >
          <Input.Password />
        </Form.Item>

        <Form.Item {...tailLayout} name="mdl" valuePropName="checked">
          <Checkbox>下次免登陆</Checkbox>
        </Form.Item>

        <Form.Item {...tailLayout}>
          <Button type="primary" htmlType="submit">
            登录
        </Button>
        </Form.Item>
      </Form>
    </div>
  }
}

// 下面是使用高阶组价后写法
// 注意箭头函数后面不能直接跟着{} 因为会它会把{} 解析为代码块
// const mapStateToProps = (state) => ({currentUser.state}) //等效于下面
const mapStateToProps = ({ currentUser }) => ({ currentUser })

// 你要把什么映射到 props  就 return 什么就可以
// return 的可以进入 Login的props 里面
// mapDispatchToProps 有一个参数 里面是dispatch 
// 注意:当写了mapDispatchToProps,默认的dispatch 就没有了
const mapDispatchToProps = function (dispatch) {
  return {
    // login方法 调用login方法传入user
    // 当return 中写入login方法,则props中存在login
    login(user) {
      dispatch({ type: 'login', user })
    },
    // 但是如果还想要原来的dispatch ,原样传过去就可以了
    dispatch
  }
  // return bindActionCreators(userAction,dispatch)
}
// connect 的两个参数的第二个参数 mapDispatchToProps 把dispatch映射到props里面
Login = connect(mapStateToProps, mapDispatchToProps)(Login)

export default Login

在这里插入图片描述

redux工作流程

在这里插入图片描述
理解:

假如在组件中有个登录按钮,登录成功后,想要修改redux上面的数据,通过dispatch来修改,使用dispatch发起一个action,那么从component 到action 就连上一条线;

然后store拿到这个action ,而reducer是创建时传入的值,所以当调用dispatch时,进入store,而store内部会自动调用reducer ,注意 reducer是一个函数,store去帮我们触发它;

reducer里面的参数,一个state,一个action ,其中state是从调用时传递过来的,action也是调用时传递过来的;

在这里插入图片描述

1、用户发出 actions

比如用户的登录登出

  store.dispatch(ation)

2、store 自动调用reducer,并且传入两个参数:当前的state收到的action,reducer会返回一个新的state

  let nextState = todoApp(previousState, action) 

3、state 一旦会变化,store 就会调用监听函数;

// 设置监听函数
  store.subscribe(listener)

4、listener 可以通过 store.getState()得到当前状态。如果使用的是react,这时可以触发渲染view;

 function listener(){
    let newState = store.getState()
    component.setState(newState)
 }

redux应用

1、用户的使用方式复杂
2、不同身份的用户有不同的使用方式(比如普通用户和管理员)
3、多个用户之间可以协作
4、与服务器大量交互,或者使用了WebSocket
5、View要从多个来源获取数据

以上根据阮一峰大佬的网站整理的笔记和引用,仅用于个人学习提升。
原网站地址:Redux 入门教程

手动实现简易版redux

/***
 * 手动实现简易版redux
 * 
*/

export const createStore = function(reducer){
  // 注意这是我自己封装的,确定需要传入一个reducer函数 
  let state = reducer()
  // 数组用来存放监听的函数
  let listeners = []

  // 实现getState
  const getState = function(){
    return state
  }

  // 实现dispatch 注意:dispatch 会自动调用reducer
  const dispatch  =function(action){
    state = reducer(state,action)

    // 发布 当state改变时,触发subscribe 有修改就有监听
    listeners.forEach(listener=>listener())
  }

  // 实现 subscribe 需要传入一个函数
  const subscribe=function(fn){
    // 订阅
    listeners.push(fn)
  }

  // 实现 replaceReducer 
  const replaceReducer = function(newReducer){
    reducer = newReducer
  }

  return {
    getState,
    dispatch,
    subscribe,
    replaceReducer
  }
}

但是用组件间的state更新原理来实现,因为给组件添加state,修改state,而且还得监听它的状态,发现 比较繁琐,而且组件的刷新除了state更新,还有props 修改来刷新组件,这里不考虑父组件的刷新,所以推荐props的方式来实现。

props修改是通过父组件来传递的,但是父组件和我的组件时分开的,想要通过redux数据从props里面传到需要的组件里面,就可以实现这种功能,之前是通过高阶组价代理props,然后传递过来;自己写也可以,但是有人已经帮我们封装写好了;就是下面的react-redux;

react-redux桥接工具

为了方便使用,Redux 的作者封装了一个 React 专用的库 React-Redux,它提供了一些接口,用于Redux的状态和React的组件展示结合起来,以用于实现状态与视图的一一对应;

连接react组件与redux状态

原理:利用context + 高阶组件实现

  1. 通过 Provider 组件共享 store 状态

  2. 利用 connect() 高阶组件接收 store 数据

通过props把需要的数据传入目标组件

使用如下

安装 npm i react-redux -D

  // 内部已经封装好 内部原理
  const MyContext = React.createContext()
  <MyContext.Provider />
  
  // 直接用
 <Provider store={store}></Provider>

  // App.js
  // mapStateToProps: 映射redux上的state到组件的props
  App = connect(mapStateToProps, mapDispatchToProps)(App);

使用 react-redux 步骤

1、引入 import { Provider } from 'react-redux'

2、对根组件进行修改 通过 Provider 共享store数据

import React from 'react'
import ReactDOM from 'react-dom'

// 用于 redux
import { Provider } from 'react-redux'
import store from './store'

// 安装路由 react-router 和 react-router-dom
import {
  HashRouter,
  BrowserRouter,
  Route
} from 'react-router-dom'

import App from './App'

// 编译时修改路由:hash路由、history路由
const Router = process.env.NODE_ENV === 'product' ? BrowserRouter : HashRouter

// 下面加了Provider之后,里面的store 底下所有子组件都可以共用store的数据
ReactDOM.render(
  <Provider store={store}>
    <Router>
      <App />
      {/* <Route component={App} /> */}
      {/* 这里不加path 因为所有路径都要渲染这个App组件 */}
    </Router>
  </Provider>,
  document.querySelector('#app')
)

3、App.js 组件 中使用 connect 高阶组件接收 store 数据

import React from 'react'
// 安装react-router 和 react-router-dom
import { Route, Redirect, Switch, NavLink, withRouter } from 'react-router-dom'

// 引入react-redux 中的 connect 
import { connect } from 'react-redux'
import store from './store/index.js'
import { logout } from './store/actions/user'


// 导入路由 页面组件
import Home from './pages/Home.jsx'
// import Good from './pages/Good.jsx'
import Search from './pages/Search.jsx'
import Profile from './pages/Profile.jsx'
import Category from './pages/Category.jsx'
import Login from './pages/Login.jsx'
import Reg from './pages/Reg.jsx'
import IQ from 'pages/IQ.jsx'

// 导入 antd 样式
import { Button, Menu, Row, Col } from 'antd'
import { HomeOutlined, ContactsOutlined, TeamOutlined, UserOutlined } from '@ant-design/icons'

// 导入样式
import './App.css'

// 高阶组件的使用 和下面ES7 装饰器一起使用
const mapStateToProps = function (state) {
  return {
    currentUser: state.user
  }
}
const mapDispatchToProps = function (dispatch) {
  return {
    logout() {
      // 注意这里的logout() 是生成的action
      dispatch(logout())
    }
  }
}

// ES7 装饰器写法 
@connect(mapStateToProps, mapDispatchToProps)
@withRouter

// 有状态改变用class组件
class App extends React.PureComponent {
  state = {
    menu: [{
      text: '首页',
      name: 'home',
      path: '/home',
      icon: <HomeOutlined />
    },
    {
      text: '搜索',
      name: 'search',
      path: '/search',
      icon: <TeamOutlined />
    }, {
      text: '分类',
      name: 'category',
      path: '/category',
      icon: <ContactsOutlined />
    }, {
      text: '我的',
      name: 'profile',
      path: '/profile',
      icon: <UserOutlined />
    }],
    current: '/'
  }

  goto = ({ key }) => {
    // console.log(this.props) // 这里的打印的是跳转路由后, 渲染的组件的 props
    // 跳转路由 有浏览器记录
    // this.props.history.push(path)

    // 跳转路由 无浏览器记录
    // this.props.history.replace(path)

    this.setState({
      current: key
    })
    this.props.history.push(key)
  }
  gotoLR = (path) => {
    this.props.history.push(path)
  }
  componentWillMount() {
    // console.log("location=", this.props.location)
    // console.log("props", this.props)
    const { pathname } = this.props.location
    if (pathname === "/") {
      this.setState({
        current: '/home'
      })
    } else {
      this.setState({
        current: pathname
      })
      // console.log("pathname=", pathname)
    }
  }

  // componentWillUnmount(){
  //   console.log("unmont=",this.props)
  //   const { pathname } = this.props.location
  //   this.setState({
  //     current: pathname
  //   })
  // }

  render() {
    const { menu, current } = this.state
    // console.log('app.props=', this.props)
    const { currentUser,logout } = this.props
    // console.log('app.props=',this.props) // 这里的打印的是当前渲染的组件的 props
    // 这时当main.js中为 <App /> 时能打印出history对象,因为已经使用高阶组件进行了包装

    return (
      <div>
        <Row>
          <Col span={12}>
            <Menu onClick={this.goto} defaultSelectedKeys={[current]} selectedKeys={[current]} mode="horizontal">
              {
                menu.map(item => <Menu.Item key={item.path} >
                  {item.icon}
                  {item.text}
                </Menu.Item>)
              }
            </Menu>
          </Col>
          <Col span={12} style={{ textAlign: 'right' }}>
            {
              currentUser._id ? <Button type="link" onClick={logout}>退出</Button> :
                <>
                  <Button type="link" onClick={this.gotoLR.bind(this, '/login')}>登录</Button>
                  <Button type="link" onClick={this.gotoLR.bind(this, '/reg')}>注册</Button>
                </>
            }
          </Col>
        </Row>
        <Switch>
          <Route path="/home" component={Home}></Route>
          {/* <Route path="/good" component={Good}></Route> */}
          <Route path="/search" component={Search}></Route>
          <Route path="/profile" component={Profile}></Route>
          <Route path="/category" component={Category}></Route>
          <Route path="/iq/:id" component={IQ} />
          <Route path="/login" component={Login}></Route>
          <Route path="/reg" component={Reg}></Route>
          <Route path="/notfound" render={() => <div>404</div>}></Route>
          {/* 重定向可以不加to */}
          <Redirect from="/" to="/home" exact></Redirect>

          {/* 404 */}
          <Redirect to="/notfound"></Redirect>
        </Switch>
        {/* <nav>
          <ul>
            {
              // 声明式导航
              // menu.map(item=><li key={item.name}>
              //   <NavLink to={item.path} activeClassName="active" activeStyle={{fontWeight:"bold"}}>{item.text}</NavLink>
              // </li>)

              // 编程式导航
              menu.map(item => <li key={item.name} onClick={this.goto.bind(this, item.path)}>
                {item.text}
              </li>)
            }
          </ul>
        </nav> */}
      </div>
    )
  }
}

// // 高阶组件(包装函数) 下面是不使用 ES7装饰器 的写法
// App = withRouter(App)

// // 你要把什么映射到 props  就 return 什么就可以
// // return 的可以进入 App的props 里面
// // mapStateToProps 有一个参数 里面是state 是 redux 的状态
// const mapStateToProps = function (state) {
//   // state:redux 的状态
//   return {
//     currentUser: state.currentUser
//   }
// }

// // connect里面有两个参数 当然这个参数可以是 a ,不一定是mapStateToProps
// App = connect(mapStateToProps)(App)
// // 这里的参数 mapStateToProps 是一个函数

export default App

4、login.js 组件中使用 connect 高阶组件

import React from 'react'
import CryptoJS from 'crypto-js'
import request from '@/utils/request'

// 引入高阶组件 connect
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import userAction, { login } from '../store/actions/user.js'

import { Form, Input, Button, Checkbox } from 'antd';
const layout = {
  labelCol: { span: 4 },
  wrapperCol: { span: 16 },
};
const tailLayout = {
  wrapperCol: { offset: 4, span: 16 },
};

// 类组件
class Login extends React.Component {

  // 成功时
  onFinish = async ({ username, password, mdl }) => {
    password = CryptoJS.SHA256(password)
    password = CryptoJS.enc.Hex.stringify(password)
    const data = await request.get('user/login', {
      username,
      password,
      mdl
    })
    // console.log('Success:', data);
    if (data.status === 200) {
      this.props.history.push('/profile')
      // localStorage.setItem('currentUser', JSON.stringify(data.data))

      console.log("login.props=", this.props)
      // 这里 就不用写 props.dispatch({type:'login},user:data.data)
      // 当使用高阶组件 mapDispatchToProps 写下面这种即可
      this.props.login(data.data)
    }
  };

  // 失败时
  onFinishFailed = errorInfo => {
    console.log('Failed:', errorInfo);
  };

  // 校验
  rules = {
    username: [{ required: true, message: '用户名必填' }],
    password: [{ required: true, message: '密码必填' }],
  }

  render() {
    return <div>
      <div style={{ margin: 10, fontSize: 20 }}>用户登录</div>
      <Form
        {...layout}
        name="basic"
        initialValues={{ remember: true }}
        onFinish={this.onFinish}
        onFinishFailed={this.onFinishFailed}
      >
        <Form.Item
          label="用户名"
          name="username"
          rules={this.rules.username}
        >
          <Input />
        </Form.Item>

        <Form.Item
          label="密码"
          name="password"
          rules={this.rules.password}
        >
          <Input.Password />
        </Form.Item>

        <Form.Item {...tailLayout} name="mdl" valuePropName="checked">
          <Checkbox>下次免登陆</Checkbox>
        </Form.Item>

        <Form.Item {...tailLayout}>
          <Button type="primary" htmlType="submit">
            登录
        </Button>
        </Form.Item>
      </Form>
    </div>
  }
}

// 高阶组件
// 注意箭头函数后面不能直接跟着{} 因为会它会把{} 解析为代码块
// const mapStateToProps = (state) => ({currentUser.state}) //等效于下面 注意默认的是存在dispatch
const mapStateToProps = ({ currentUser }) => ({ currentUser })

// 你要把什么映射到 props  就 return 什么就可以
// return 的可以进入 Login的props 里面
// mapDispatchToProps 有一个参数 里面是 dispatch 
// 注意:当写了mapDispatchToProps,默认的dispatch 就没有了
const mapDispatchToProps = function (dispatch) {
  return {
    // login方法 调用login方法传入user
    // 当return 中写入login方法,则props中存在login
    login(user) {
      dispatch({ type: 'login', user })
    },
    // 但是如果还想要原来的dispatch ,原样传过去就可了
    dispatch
  }
  // return bindActionCreators(userAction,dispatch)
}
// connect 的两个参数的第二个参数 mapDispatchToProps 把dispatch映射到props里面
Login = connect(mapStateToProps, mapDispatchToProps)(Login)

export default Login

在 login 组件触发了dispatch 就会进入到redux的reducer里面,把state进行修改,而state通过props传入到了App组件,这时props在App中有修改(通过高阶组件修改的)而props有修改,组件就会刷新;然后登陆注册按钮 就会改为 退出按钮,发现监听都不用写;

redux模块化

模块化为了方便管理,分工明确

redux模块化 也可以成为 reducer模块化

目录如下:
在这里插入图片描述
1、store 文件夹下新建一个reducers 文件夹,里面新建文件如目录 ;
2、common.js 全局配置、user.js 用户配置、cart.js 购物车配置等等;
3、把多个reducer合并为一个reducer,通过redux的一个方法:combineReducers()
4、在reducers 的下面的 index.js中合并 reducer

user.js

// 刷新时,先从localstorage 中获取用户信息
let currentUser = localStorage.getItem('currentUser') //null
try {
  currentUser = JSON.parse(currentUser) || {}
} catch (err) {
  currentUser = {}
}

const initState = {
  ...currentUser
}

function reducer(state=initState,action){
  // console.log("action",action)
  switch(action.type){
    // 登录
    case 'login':
      localStorage.setItem('currentUser',JSON.stringify(action.user))
      return action.user
      
    // 登出
    case 'logout':
      localStorage.removeItem('currentUser')
      return {}

    // 更新用户
    case 'update_user':
      // console.log(1)
      return {
        ...state,
        ...action.user
      }
    default:
      return state
  }
}

export default reducer

cart.js

const initState = {
  goodlist:[],
  totalPrice:0
}

function reducer(state=initState,action){
  switch(action.type){
    // 添加商品到购物车
    // {type:'add_to_cart',goods}
    case 'add_to_cart' :
      return {
        ...state,
        goodlist:[action.goods,...state.goodlist]
      }
    
    // 从购物车移除商品
    // {type:'remove_from_cart',id}
    case 'remove_from_cart' :
      return {
        ...state,
        goodlist:state.goodlist.filter(item=>item.id!==action.id)
      }

    // 清空购物车所有商品
    // {type:'clear_cart'}
    case 'remove_from_cart' :
      return {
        ...state,
        goodlist:[]
      }
    default:
      return state
  }
}

export default reducer

common.js

const initState = {
  showMenu:true
}

function reducer(state=initState,action){
  switch(action.type){
    // 菜单栏显示
    case 'show_menu' :
      return {
        ...state,
        showMenu:action.show
      }
    default:
      return state
  }
}

export default reducer

reducers下面的 index.js

import {combineReducers} from 'redux'

import cartReducer from './cart'
import userReducer from './user'
import commonReducer from './common'

// 把多个reducer合并成一个reducer
const reducer = combineReducers({
  cart:cartReducer,
  user:userReducer,
  common:commonReducer,
})

export default reducer

store下面的index.js

/**
 * redux核心概念
 *   store
 *   reducer   state的修改逻辑
 *     参数
 *       state
 *       action
 *     action
 *       格式:{type:''}
 */

// 引入reducers 文件下面的 index.js
import reducer from './reducers'

// 模块化 : 把这部分的reducer提取出去
// // 这个应用的初始状态,可以作为state的默认值
// const initState = {
//   currentUser: {},
//   // 默认显示菜单
//   showMenu: true,
// }
// // reducer必须为纯函数
// // dispatch({type:'changeUser',user})
// // dispatch({type:'changeMenu'})
// // reducer 是一个函数,它接受 当前state和 收到的action作为参数,返回一个新的state
// const reducer = function (state = initState, action) {
//   // console.log('reducer', state, action)
//   switch (action.type) {
//     case 'login':
//       return {
//         ...state,
//         currentUser: action.user
//       }
//     case 'logout':
//       return {
//         ...state,
//         currentUser:{}
//       }
//     case 'showmenu':
//       return {
//         ...state,
//         showMenu: action.showMenu
//       }
//     default:
//       return state
//   }
// }


//实际应用中,Reducer 函数不用手动调用,store.dispatch方法会触发 Reducer 的自动执行。
//为此,Store 需要知道 Reducer 函数,做法就是在生成 Store 的时候,将 Reducer 传入createStore方法。
const store = createStore(reducer)
// 上面代码中,createStore接受 Reducer 作为参数,生成一个新的 Store。
// 以后每当store.dispatch发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State。

export default store

// 3. 使用
// 获取:store.getState()
// 修改:store.dispatch(action)
// 监听:store.subscribe(fn)

Action Creator 用于简化代码操作

  • Action Creator

每次编写action对象比较麻烦,可以封装一个函数用于生成action
action creator 一个用于创建action的函数

    export function updateCart(todo){
        return {
            type:'UPDATE_CART',
            payload:todo
        }
    }

目录中的store下的actions文件夹的user.js

// 用户的登录和登出的dispatch操作 比如 dispatch({type:'xxxx'})
export function login(user) {
  return {
    type: 'login',
    user
  }
}

export function logout() {
  return {
    type: 'logout'
  }
}

export default {
  login,
  logout
}
// 假如用这个export default形式的导出 如果在login组件中使用,可以这样写:
// import userAction from '../store/actions/user.js'
// 或者 
// import { login } from '../store/actions/user.js'

login.js组件中使用 action Creator

import userAction, { login } from '../store/actions/user.js'


// 注意箭头函数后面不能直接跟着{} 因为会它会把{} 解析为代码块
// const mapStateToProps = (state) => ({currentUser.state}) //等效于下面
const mapStateToProps = ({ currentUser }) => ({ currentUser })

// 你要把什么映射到 props  就 return 什么就可以
// return 的可以进入 Login的props 里面
// mapDispatchToProps 有一个参数 里面是dispatch 
// 注意:当写了mapDispatchToProps,默认的dispatch 就没有了
const mapDispatchToProps = function (dispatch) {
  return {
    // login方法 调用login方法传入user
    // 当return 中写入login方法,则props中存在login
    login(user) {
      dispatch(login(user))
    },
    // 但是如果还想要原来的dispatch ,原样传过去就可了
    dispatch
  }
  // return bindActionCreators(userAction,dispatch)
}
// connect 的两个参数的第二个参数 mapDispatchToProps 把dispatch映射到props里面
Login = connect(mapStateToProps, mapDispatchToProps)(Login)

  • bindActionCreators

利用redux的bindActionCreatorsActionCreator中默认导出的所有方法 ( 注意是 export default中的方法) 绑定到组件props并自动隐式调用dispatch(action)

    import {bindActionCreators} from redux;
    import {connect} from 'react-redux';
    import ActionCreator from 'actions';

    //...
    MyComponent = connect(state=>state,dispatch=>bindActionCreators(ActionCreator,dispatch))(MyComponent)
    

login.js组件中使用 bindActionCreators

import { bindActionCreators } from 'redux'
// 注意:使用bindActionCreators  要使用默认导出的 export default 下面的 userAction 就是默认导出的
import userAction, { login } from '../store/actions/user.js'


// 注意箭头函数后面不能直接跟着{} 因为会它会把{} 解析为代码块
// const mapStateToProps = (state) => ({currentUser.state}) //等效于下面
const mapStateToProps = ({ currentUser }) => ({ currentUser })

// 你要把什么映射到 props  就 return 什么就可以
// return 的可以进入 Login的props 里面
// mapDispatchToProps 有一个参数 里面是dispatch 
// 注意:当写了mapDispatchToProps,默认的dispatch 就没有了
const mapDispatchToProps = function (dispatch) {
  // return {
  //   // login方法 调用login方法传入user
  //   // 当return 中写入login方法,则props中存在login
  //   login(user) {
  //     dispatch({ type: 'login', user })
  //   },
  //   // 但是如果还想要原来的dispatch ,原样传过去就可了
  //   dispatch
  // }
  
  // 直接用下面的写法就可以,不用上面 return 的写法 dispatch不用写,里面login() 也不用写了
   return bindActionCreators(userAction,dispatch)
    // {login,logout} 等效于下面的代码 内部帮我们实现了以下代码
    // { 
    //   login : function(user){
    //    dispatch(login(user))
    //   },
    //   logout : function(){
    //     dispatch(logout())
    //   }
    // }
}
// connect 的两个参数的第二个参数 mapDispatchToProps 把dispatch映射到props里面
Login = connect(mapStateToProps, mapDispatchToProps)(Login)

redux刷新浏览器,数据就消失,为了持久化需要配合本地存储

比如登录时,把用户信息存在本地存储里面

// 刷新时,先从localstorage 中获取用户信息
let currentUser = localStorage.getItem('currentUser') //null
try {
  currentUser = JSON.parse(currentUser) || {}
} catch (err) {
  currentUser = {}
}

const initState = {
  ...currentUser
}

function reducer(state=initState,action){
  // console.log("action",action)
  switch(action.type){
    // 登录
    case 'login':
      localStorage.setItem('currentUser',JSON.stringify(action.user))
      return action.user
      
    // 登出
    case 'logout':
      localStorage.removeItem('currentUser')
      return {}

    // 更新用户
    case 'update_user':
      // console.log(1)
      return {
        ...state,
        ...action.user
      }
    default:
      return state
  }
}

export default reducer

在这里插入图片描述
收藏功能的实现

import React from 'react'
import request from '@/utils/request'
import { connect } from 'react-redux'

import { Avatar, Button, Rate, message } from 'antd'
// 小图标
import { UserOutlined, HeartOutlined } from '@ant-design/icons'
// 富文本编辑器
import E from 'wangeditor'

@connect(state => ({
  currentUser: state.user
}), (dispatch) => ({
  updateUser(user) {
    dispatch({ type: 'update_user', user })
  }
}))

class IQ extends React.PureComponent {
  state = {
    data: {},
    // editorContent: ''
  }

  follow = async () => {
    const { data } = this.state
    // console.log("data_id",data._id)
    const { currentUser, updateUser } = this.props
    // console.log("updateUser",updateUser)
    // console.log("issc",currentUser.focus.some(item => item === data._id)) //能打印false或true
    if (Object.keys(currentUser).length === 0) {
      message.error("请登录后收藏")
      return false
    }
    // 判断是否收藏
    const issc = currentUser.focus.some(item => item === data._id)
    const result = await request.put(`iq/${data._id}/follow`, {
      userid: currentUser._id,
      action: issc ? 'del' : 'add'
    })
    // console.log("result",result)
    if (result.status === 200) {
      if (issc) {
        // 取消收藏
        // this.setState({
        //   data: {
        //     ...data,
        //     focus: data.focus.filter(item => item !== data._id)
        //   }
        // })
        updateUser({
          focus: currentUser.focus.filter(id => id !== data._id)
        })
      } else {
        //添加收藏
        // this.setState({
        //   data: {
        //     ...data,
        //     focus: [data._id,...data.focus]
        //   }
        // })
        // console.log("data_id", data._id, currentUser.focus)
        updateUser({
          focus: [data._id, ...currentUser.focus]
        })
        // console.log("data_id", data._id, currentUser.focus)
      }
    }
  }

  async componentWillMount() {
    // console.log("props=", this.props)
    // console.log("componentWillMount",this.state)
    const { id } = this.props.match.params
    const data = await request.get('/iq/' + id)
    // console.log("data=", data)
    this.setState({
      data: data.data
    })
  }

  async componentDidMount() {
    // console.log(this.props)
    const { id } = this.props.match.params
    const data = await request.get('/iq/' + id)
    this.setState({
      data: data.data
    })

    // 更新用户浏览记录
    const { status } = await request.get(`/iq/${id}/hot`)
    // console.log("status",status)
    if (status === 200) {
      this.setState({
        data: {
          ...this.state.data,
          hot: this.state.data.hot + 1
        }
      })
    }

  render() {
    // console.log("props=",this.props)
    const { data } = this.state
    const { currentUser } = this.props
    // console.log("user", data)
    // console.log("iq",currentUser)
    // console.log("收藏",currentUser.focus.some(item=>item === data._id) ? '取消' : '收藏')
    return (
      <div>
        <div style={{ marginLeft: 20 }}>
          <h1>{data.question}</h1>
          <div>
            {
              data.user
                ? <span><Avatar size="small" icon={<UserOutlined />} />{data.user.username}</span>
                : ''
            }
            <span style={{ margin: 20 }}>{data.hot}浏览</span>
            <span>评分<Rate disabled value={data.difficulty} /></span>
            {
              // Object.keys(currentUser).length !== 0
              data.user !== null
                ?
                <Button onClick={this.follow} style={{ margin: 20 }} type="link" icon={<HeartOutlined />}>
                  {currentUser.focus.some(item => item === data._id) ? '取消' : '收藏'}
                </Button>
                :
                <Button onClick={this.follow} style={{ margin: 20 }} type="link" icon={<HeartOutlined />}>收藏</Button>
            }

          </div>
        </div>
    )
  }
}

export default IQ;

redux 中间件 (只要有异步就在中间件中处理)

有没有发现我们上面的数据处理都是同步的,都是数据回来之后,直接修改state,但是在项目中可能存在大量的异步操作,可能有人会说在reducer里面进行操作也可以,但是不建议这样做,因为reducer就是一个纯函数,不应该搞的复杂;

那么下面引入需要用到的 中间件middlewore
在vue中使用axios可以解决异步的问题,比如在vuex中异步处理,但是在react中没有这种处理的方式所以不可能处理异步,只能用中间件来处理;

什么是中间件?在操作到达目的之前进行的拦截操作,就是中间处理;
在这里插入图片描述
所有的异步操作都会放到中间件处理,比如ajax请求,请求的数据完成后,在中间件里面发起dispatch ,然后进入reducer中,再修改state

那怎么从第一图变为第二个图呢?

在store文件下的index.js中

// 2. 创建仓库  也就是说如果往第三个参数传 中间件 那么代码会从第一个图变为第二图了
const store = createStore(reducer,initState, middleware)

redux中的action仅支持原始对象(plain object),处理有副作用的action,需要使用中间件。中间件可以在发出action,到reducer函数接受action之间,执行具有副作用的操作

中间件的由来与原理、机制

对 dispatch 改装,实现 redux 异步处理中间件

常用中间件

  • redux-chunk

  • redux-promise

  • redux-saga(推荐使用)

    • Generator 生成器函数
      • 返回一个迭代器 Iterator

      • yield 暂停 (后面可以写ajax请求,类似于await效果)

      • return 结束

      • Iterator 迭代器(遍历)

        • next() 返回格式为:{value,done}的对象

        • value: 迭代到当前位置的值

        • done: 迭代器是否迭代完毕

      • for…of 能遍历具有迭代器特性的数据

    内部会自动调用迭代器的next方法

    • Effect
      • call
      • apply
      • put
      • takeEvery
      • takeLatest

使用中间件

  1. 利用 applyMiddleware 接受中间件(可同时接受多个中间件)
  2. 通过 createStore 的第3个参数连接中间件与store
    import {createStore,applyMiddleware} from 'redux';
    // 引入 saga
    import createSagaMiddleware from 'redux-saga';
    
    // 1.引入自定义saga配置文件
    import rootSaga from './rootSaga.js';

    // 2.创建saga中间件
    const sagaMiddleware = createSagaMiddleware();

    // 3.将 sagaMiddleware 连接至 Store
    let enhancer = applyMiddleware(sagaMiddleware)
    const store  = createStore(reducer,enhancer);

    // 4.运行 Saga配置
    sagaMiddleware.run(rootSaga);

多个 enhancer 使用 redux.compose 组合成单个 enhancer

    import {createStore,applyMiddleware,compose} from 'redux'
    let enhancer = compose(...enhancer);
    // 注意 这里的 enhancer 就是第三个参数,它内部会判断传入的参数 enhancer是 函数类型
    // 而第二个参数是state,是对象类型 {},
    const store  = createStore(reducer,[state,] enhancer);

或者:

/**
 * redux核心概念
 *   store
 *   reducer   state的修改逻辑
 *     参数
 *       state
 *       action
 *     action
 *       格式:{type:''}
 */

// 1. 引入redux
import {createStore, applyMiddleware} from 'redux'
import reducer from './reducers'
import {composeWithDevTools} from 'redux-devtools-extension'

// 引入saga 
import mysaga from './saga'

// saga 使用步骤1:1、引入saga
import createSagaMiddleware from 'redux-saga'

// saga 使用步骤2:创建saga中间件 注意不能直接写入createStore中,需要用到redux中的applyMiddleware
const sagaMiddleware = createSagaMiddleware()

// 2. 创建仓库
// saga 使用步骤3:把saga 中间件与store进行结合
let enhancer = applyMiddleware(sagaMiddleware)

enhancer = composeWithDevTools(enhancer)
 // 注意 这里的 enhancer 是第三个参数,它内部会判断传入的参数 enhancer是 函数类型
 // 而第二个参数是state,是对象类型 {},const store = createStore(reducer,[state,] enhancer)
const store = createStore(reducer, enhancer)

// saga 使用步骤4:运行saga配置 下一步就是怎么写saga配置
sagaMiddleware.run(mysaga)

export default store

新建一个saga文件夹下面的index.js 用于saga配置

/***
 * saga 配置
 * 使用 es6新特性 : Generator
 * 
*/

import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
import request from '@/utils/request'

function* getUser(action){
  console.log('getUser=',action) // {type:'update_user_async'}
  //注意这个action是传入过来的参数  通过this.props.dispatch({type:'get_user',userid:xxxxxx})
  // 这样就可以通过action拿到用户的id 
  const {data} = yield request.get('/user/'+action.userid)
  console.log('user=',data)
  /*
    拿到用户的数据 然后修改state  可以 store.dispatch({type: 'update_user',user:data})
    但是 dispatch 从哪里来呢? 可以 import store from '@/store'
  */
  // 其实不用上面的方式也可以解决,saga 里面有个put,而put 就是dispatch 
  // yield put({type:'update_user',user:data}) 
  // 改为下面写法更好
  yield put({type:action.type.replace('_async',''),user:data})
}
function* getNewIQ(){

}

function* init(){
  console.log('init')
  // 监听用户 dispatch 操作 这里加_async 代表是异步的意思 那前面发送的也应该是update_user_async
  // 比如在 profile的 钩子函数 componentDidMount中,this.props.dispatch({type:'get_user_async',userid:'xxxx'})
  // 建议上面和下面的action写的一样,
  yield takeLatest('update_user_async',getUser)
  yield takeLatest('get_newid_async',getNewIQ)
  /*
     takeEvery, takeLatest 本质一样,但是 takeLatest 只保留最后一次的触发,
     比如前面的dispatch,是点击按钮才触发,但是不小心点击多次,会连续触发多次,而用takeEvery会触发多次
     而用 takeLatest  只会保留最后一次,算是像 经常见到的 节流和防抖 操作,建议用 takeLatest 
  */
}

export default init
  • sagaAction

    sagaAction = reducerAction + ‘_async’

  • reducerAction

补充一点:就是单元测试,把上面代码改进一下

```javascript
/***
 * saga 配置
 * 使用 es6新特性 : Generator
 * 
*/

import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
import request from '@/utils/request'

// 单元测试 利用 apply 或者 call 
function* getUser(action){
  // call 是把要执行的作为参数传入,内部会把调用request方法,
  // 但是 request需要参数,继续往里面传入参数即可,效果时一样的,
  // 最终 调用request 并把参数传入request 里面 
  // 这个call就为了单元测试
  const {data} = yield call(request.get,'/user/'+action.userid)
  
  // saga 内部会自动调用 next()
  yield put({type:action.type.replace('_async',''),user:data})
}
function* getNewIQ(){

}

function* init(){
  yield takeLatest('update_user_async',getUser)
  yield takeLatest('get_newid_async',getNewIQ)
}

export default init

利用中间件调式Redux程序

  1. 在谷歌应用商店下载 redux-devtools
  2. 安装redux-devtools-extension
    npm install -save-dev redux-devtools-extension
  1. 引入并使用
    // 单独使用
    import {composeWithDevTools } from 'redux-devtools-extension';
    const store = createStore(rootReducer,composeWithDevTools());
    export default store;

    // 与saga一起使用
    import {createStore,applyMiddleware,compose} from 'redux'
    import rootSaga from './rootSaga.js';
    const sagaMiddleware = createSagaMiddleware();

    let enhancer = composeWithDevTools(applyMiddleware(sagaMiddleware))
    // 或使用compose
    // let enhancer = compose(applyMiddleware(sagaMiddleware),composeWithDevTools())

    const store = createStore(rootReducer,enhancer)
    export default store;
  1. 在Chrome浏览器中调试redux程序
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值