状态管理-react

状态管理

Flux

核心组成部分

  • Store
    • 存储状态(数据)
    • 通过事件的订阅和发布更新视图
  • Views
    • 视图,使用React 组件充当
  • Actions
    • 动作创建者
      • 视图中的数据交互和用户交互
  • Dispatch
    • 派发器
      • 修改数据

Flux流程

  1. 要想使用FLux架构思维,需要通过一个工具进行使用, 这个工具就是flux

    yarn add flux

  2. 在src目录下新建store文件,里面新建index.js

    • 存储数据
    • 使用事件的订阅和发布来更新视图
      • 使用node中的events模块来完成
      • on
      • emit
    const events = require( 'events' )  //使用events模块
    
    const EventEmitter = events.EventEmitter  
    
    const store = {
      ...EventEmitter.prototype, //获得其中的方法,如on
      state: { //state就是用来存储数据的
        count: 0
      },
      getState () { // 这个方法是帮助我们获取store里面的状态的
        return this.state
      }
    }
    
    export default store 
    
  3. 将store中的数据显示在组件(视图)中

      import store from './store'
    
      class xxx extends React.Component{
        constructor () {
          super()
          this.state = {
            count: store.getState().count  //获取数据
          }
        }
    
        render () {
          return (
            <div>
              <p> { this.state.count } </p>
            </div>
          )
        }
      }
    
  4. 用户操作,用户点击按钮,执行当前组件中的方法,这个方法的逻辑实际上是actionCreators中的方法

  5. 创建actionCreators.js(名字/方式不固定)

    • actions的发送要通过dispatcher来发送
        import * as type from './type'   //方法名
         import dispatcher from './dispatcher';  //引入dispatcher,可以用{  }形式
         const actionCreators = {
           increment () {
             // 创建动作
             let actions = {
               type: type.INCRMENT
             }
             // dispatcher来通过dispatch  发送actions
             dispatcher.dispatch( actions )
           }
         }
    
         export default actionCreators
    
  6. 创建dispatcher.js

     import { Dispatcher } from 'flux';
      import * as type from './type'
      import store from './state'
      const dispatcher = new Dispatcher()
    
      // dispatcher.register( callback )
    
      dispatcher.register( ( actions) => {//actions就是actions发送过来的
        switch ( actions.type ) {
          case type.INCRMENT:
              // 用户操作了
              store.state.count++
            break;
        
          default:
            break;
        }
      })
    
      export default dispatcher
    
  7. 通过store的事件的发布和订阅进行 当前组件中 state 的重新赋值

    • 以点击为案例
      • 组件的生命周期中,数据可以进行一次修改的 componentWillMount // componentDidMount
      • 当我们点击按钮的时候,就要修改当前组件的state,也就是要进行事件的订阅
     import React from 'react';
      import logo from './logo.svg';
      import './App.css';
    
      import store from './store'
      import actionCreators from './store/actionCreators';
    
      class App extends React.Component {
        
        constructor () {
          super()
          this.state = {
            count: store.getState().count
          }
        }
    
        increment () {
          actionCreators.increment()
          store.emit('count') //调用count
        }
    
        componentDidMount () {//store的订阅
          store.on('count', () => { //定义count
            this.setState({
              count: store.getState().count
            })
          })
        }
    
        render () {
          return (
            <div>
              <h3> flux </h3>
              <button onClick = { this.increment }> + </button>
              <p> count: { this.state.count } </p>
            </div>
          )
        }
      }
    
      export default App;
    

Redux

  • 使用方式
    • 数据不分块使用
    • 数据分块使用
      • 方便维护和更新

核心组成部分

  • Store 数据的管理者和数据的存储者
  • actionCreators 动作的创建者,发送动作给 reducers
  • react Components 组件( 用来充当视图层 )
  • reducers 数据的修改者,返回一个新的 newstate 给store

其他

React 只是 DOM 的一个抽象层,并不是 Web 应用的完整解决方案。有两个方面,它没涉及。

  • 代码结构
  • 组件之间的通信

2013年 Facebook 提出了 Flux 架构的思想,引发了很多的实现。2015年,Redux 出现,将 Flux 与函数式编程结合一起,很短时间内就成为了最热门的前端架构。

如果你不知道是否需要 Redux,那就是不需要它

只有遇到 React 实在解决不了的问题,你才需要 Redux

简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。

  • 用户的使用方式非常简单
  • 用户之间没有协作
  • 不需要与服务器大量交互,也没有使用 WebSocket
  • 视图层(View)只从单一来源获取数据

需要使用Redux的项目:

  • 用户的使用方式复杂 【 电商类型 后台管理系统 】
  • 不同身份的用户有不同的使用方式(比如普通用户和管理员) 【 权限验证 】
  • 多个用户之间可以协作
  • 与服务器大量交互,或者使用了WebSocket
  • View要从多个来源获取数据

从组件层面考虑,什么样子的需要Redux:

  • 某个组件的状态,需要共享
  • 某个状态需要在任何地方都可以拿到
  • 一个组件需要改变全局状态
  • 一个组件需要改变另一个组件的状态

Redux的设计思想:

  1. Web 应用是一个状态机,视图与状态是一一对应的。
  2. 所有的状态,保存在一个对象里面(唯一数据源)。

注意:flux、redux都不是必须和react搭配使用的,因为flux和redux是完整的架构,在学习react的时候,只是将react的组件作为redux中的视图层去使用了。

Redux的使用的三大原则:

  • Single Source of Truth(唯一的数据源)
  • State is read-only(状态是只读的)
  • Changes are made with pure function(数据的改变必须通过纯函数完成)

实现Redux步骤

  1. redux是一个架构思维,我们实现需要一个工具,这个工具叫做redux

    • yarn add redux
  2. 在src目录下新建store目录,里面新建index.js

     import { createStore } from 'redux'
     import reducer from './reducer'
    
     const store = createStore( reducer ) // 不加new   createStore() 参数不是一个 Object 而是一个Function
    
     export default store 
    
  3. 在store目录下新建一个state.js (数据,可直接写在reducer.js中)

      const state = {
        todos: [
          {
            id: 1,
            task: '任务一'
          }
        ]
      }
      export default state 
    
  4. 在 store下新建一个 reducer.js

      import state from './state'
      const reducer = ( previousState = state , action ) => {//赋初始值、纯函数
        const newState = {
          ...previousState    // 解构的原因是为了做深拷贝,我们操作newState,不会影响state
        }                                     //浅
        return newState
      }
    
      export default reducer 
    
    //数据分块时
    import { combineReducers } from 'redux'
    
    
    const reducer = combineReducers({
        //这里面放分片的reducer
    })
    
  5. 在你想要使用的组件中直接引用 store

    import React, { Component,Fragment } from 'react'
    import store from '../store'
    
    class Content extends Component{
        constructor () {
            super()
            this.state = {
                todos: store.getState() //获得数据
            }
        }
    
        render () {
            return (
                <Fragment>
                    <div>
                    </div>
                </Fragment>
            )
        }
    
    }
    
    export default Content 
    
  6. 进行用户交互 React component — > actionCreators,在store下新建(非硬性)actionCreators.js

    import * as type from './type'
    import store from './index'
    
    const actionCreators = {
      add_todos_item ( val ) {
        
    	//动作的创建
        const action = {
          type: type.ADD_TODOS_ITEM,
          payload: val // 负载数据
        }
    
        // 动作的发送
            store.dispatch( action )
       }
     }
     export default actionCreators
    
  7. 在pages中触发 actionCreators中 的方法

     	  import React, { Component,Fragment } from 'react'
          import actionCreators from './../store/actionCreators';
    
          class Button extends Component{
    
            add = () => {
              let val = this.input.value
              actionCreators.add_todos_item( val )  //调用动作中的方法
              this.input.value = ''
            }
    
            render () {
              return (
                <Fragment>
                  <div>
                    <input type = "text" ref = { el => this.input = el } />
                    <br/>
                    <button onClick = { this.add }> + </button>
                  </div>
                </Fragment>
              )
            }
    
          }
     
           export default Button 
    
  8. 在 reducer中修改数据

     	import state from './state'
    
        const state = require( './state' )
    
        import * as type from './type'
    
        const reducer = ( previousState = state,action) => {
          let newState = {
            ...previousState
          }
    
          //判断用户进行了那个用户交互 ,操作新状态
          switch ( action.type ) { 
            case type.ADD_TODOS_ITEM:
    
              //修改新状态
              newState.todos.push({
                id: newState.todos.length + 1,
                task: action.payload
              })
              
              break;
          
            default:
              break;
          }
        
          return newState
        }
        export default reducer 
    
  9. 进行数据个更新,通过store的订阅功能进行更新,也就是组件需要重新赋值一次数据

    • 在Content组件中进行订阅
      componentDidMount () {
          store.subscribe( () => {
            this.setState({
              todos: store.getState().todos
            })
          })
        }
    

Redux进阶

  • redux-thunk是一个redux的中间件,用来处理redux中的复杂逻辑,中间件都是对store.dispatch()的增强,比如异步请求;redux-thunk中间件可以让action创建函数先不返回一个action对象,而是返回一个函数;
  • react-redux相当于一个适配react的一个redux插件;redux本身可以在任何项目中使用,react-redux带来了更适合react的方法{ connect };
  • redux就是来管理数据的一个仓库了。
  • redux-saga是一个用于管理redux应用异步操作的中间件,redux-saga通过创建sagas将所有异步操作逻辑收集在一个地方集中处理,可以用来代替redux-thunk中间件
  • store/index.js
//创建store

import { createStore,applyMiddleware } from 'redux' //applyMiddleware是用来执行中间件 , 中间件就是一个函数
import reducer from 'reducer'

// 统一完成数据请求处理

import thunk from 'redux-thunk' //需要安装

const store = createStore( reducer,applyMiddleware( thunk ) ) //这个reducer是总的reducer 

export default store 

actionCreator的方法就可以返回一个能接收到dispatch的一个函数,我们可以在这个函数中进行异步操作之后,将actionCreator创建好的action给发送

  • reducer/index.js
/* 
  这个文件是统一的reducer,,它的作用是为了管理分片的reducer
*/

import { combineReducers } from 'redux'
import homeReducer from './homeReducer'

const reducer = combineReducers({
  // 分片的reducer 
  homeReducer
})

export default reducer 
  • reducer/homeReducer
import { 
  MOVIE_ON_INFO_LIST
} from 'action/actionType'  //引入动作类型

const initState = { //初始化数据
  movieIds: null,
  movieLists: null
}

const homeReducer = ( state = initState,action ) => {
  const newState = {
    ...state
  }

  // 判断用户是进行了什么动作
  switch ( action.type ) {
    case MOVIE_ON_INFO_LIST:
      //修改数据
      newState.movieIds = action.payload.data.movieIds
      newState.movieLists = action.payload.data.movieList
      break;
  
    default:
      break;
  }

  return newState
}

export default homeReducer 
  • action/index.js
import { getMovieOnInfoList } from './homeAction'

const actions = {
  getMovieOnInfoList
}

export default actions
  • export const MOVIE_ON_INFO_LIST = 'MOVIE_ON_INFO_LIST' actionType.js
  • action/homeAction.js
import request from 'utils/request' //封装好的axios
import api from 'api' //接口
import { MOVIE_ON_INFO_LIST } from './actionType';

export const getMovieOnInfoList = () => {
  // 先进行数据请求,然后在创建动作,发送动作 , 
  
  // 注意: 使用了redux-thunk之后要求返回一个参数为dispatch的回调函数
  return  async dispatch => {
    const result = await request({ //数据请求
      url: api.home.list,
      params: {
        token: ''
      }
    })

    const action = {
      type: MOVIE_ON_INFO_LIST,
      payload: result 
    }

    dispatch( action ) //发送动作给reducer

  }
}
  • 使用的组件
import React, { Component } from 'react';
import actions from 'action'  //引入动作
//import { 动作名 } from 'action'  //引入具体动作
import { connect } from 'react-redux'   // 需要安装
import { bindActionCreators } from 'redux'

//使用的时候调用actions对象中的方法
 this.props.getMovieOnInfoList()
//调用的时候   homeReducer为名字
 this.props.homeReducer.xxx

const mapStateFromProps = state => {
  return state //传递state
}

const mapDispatchToProps = dispatch => {
  return bindActionCreators( actions,dispatch )
}

//使用connect
export default connect( mapStateFromProps,mapDispatchToProps )(Home) 
  • react-redux

    src目录下index.js修改

    import store from 'store'
    
    import { Provider }  from 'react-redux'
    
    ReactDOM.render(
      <Provider store = { store }>
        <Router>
          <App />
        </Router>
      </Provider>
      , document.getElementById('root'));
    

    react-redux提供两个核心的api:

    • Provider: 提供store
    • connect: 用于连接容器组件和展示组件
    1. Provider

      根据单一store原则 ,一般只会出现在整个应用程序的最顶层。(容器组件)

    2. connect

      语法格式为

      connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)(component)

      一般来说只会用到前面两个,它的作用是:

      • store.getState()的状态转化为展示组件的props
      • actionCreators转化为展示组件props上的方法

    特别强调:

    官网上的第二个参数为mapDispatchToProps, 实际上就是actionCreators

    只要上层中有Provider组件并且提供了store, 那么,子孙级别的任何组件,要想使用store里的状态,都可以通过connect方法进行连接。如果只是想连接actionCreators,可以第一个参数传递为null

  • src/api 后端接口文档

路由监听

  • 可以在LayOut中做统一的路由监听
componentDidMount () {
    // 这个钩子我们用来监听页面第一次加载
    this.checkLogin()
    this.watchTabBar()
  }

  componentWillReceiveProps ( nextProps ) {
    //这个钩子可以监听到 页面中路由的切换 
    this.watchTabBar( nextProps ) //nextProps
  }

  checkLogin = () => {
    const token = localStorage.getItem( 'token' )
    if ( !token ) {
      this.props.history.push('/err')
    } 
  }
  watchHead = () => {
    //有的页面有头部,有的页面没有头部
  }
  watchTabBar = ( nextProps ) => {
    // 有的页面有底部,有的页面没有底部
    // 底部没有的有   mine  login    register 
      										//判断是刷新还是跳转页面
    const { pathname } = nextProps &&  nextProps.location || this.props.location
    const arr = ['/mine','/account/login','/account/register','/recommend']
    const f = arr.some( item => item == pathname )
     if ( f ) {
       //条件成立,证明我们在当前路由上,需要去掉底部
       this.setState({
         tab_bar_flag: false
       })
     } else {
       this.setState({
         tab_bar_flag: true
       })
     }
  }

better-scroll上拉加载事件

import BScroll  from 'better-scroll'  //安装

import _ from 'loadsh' //分组工具

componentDidMount () {
    this.bscroll = new BScroll('.home-list',{
      click: true, // 解锁页面的点击(原因:有一层覆盖)
      pullUpLoad: {// 开启上拉加载,50表示拉动的距离是50时进行上拉的事件的触发
        threshold: 50
      },
      pullDownRefresh:{//下拉刷新
                threshold:50,
                stop:30
            }
      
    })

    // 上拉加载的事件
    let count = 0  //计数,用作判断

    this.bscroll.on('pullingUp',() => {
      Toast.loading('Loading...', 1 );

      // 触发多次
      // 重新发起数据请求,修改数据
      // 将id进行数据分组 - 使用loadsh来完成
      // const id = _.chunk( 数组,几个分一组 )
      const id =  this.props.homeReducer.movieIds && _.chunk( 						this.props.homeReducer.movieIds.slice( 12 ), 10 )
   
     const idStr = id[ count ].join(',') //传入参数形式要求

      if ( count < id.length - 1 ) {
        //表示可以拉动
        this.props.moreComingMovies( idStr ) // id是第一次数据请求之后的所有(id - 12 ) / 10 
        count ++ 
      }

      if ( id.length - 1 ==  count ) {
        // 拉动结束了,没有数据了
       console.log('拉动结束了')
      }

      this.bscroll.finishPullUp() //一次拉动的结束
    })
    
     this.bscroll.on('pullingDown',async()=>{
            Toast.success('已成功为您推荐新的内容!',1)
             count + 8 < 99 ? count+=8 : count=count-99+8
             const video = await http({
                url:'/video/group',
                params:{
                    id: this.state.idList[count].id
                }
            })
            // console.log('111',video.data.datas)
            this.setState({
                videos:video.data
            })
            // console.log('shang',count)
            this.bscroll.finishPullDown();
            
        })
  }

axios拦截器

// Add a request interceptor
axios.interceptors.request.use(function (config) {
    // Do something before request is sent
    //这里写加载之前
    return config;
  }, function (error) {
    // Do something with request error
    return Promise.reject(error);
  });
 
// Add a response interceptor
axios.interceptors.response.use(function (response) {
    // Do something with response data
    return response;
  }, function (error) {
    // Do something with response error
    return Promise.reject(error);
  });

Mobx

当使用 mobx-react 时可以定义一个新的生命周期钩子函数 componentWillReact。当组件因为它观察的数据发生了改变,它会安排重新渲染,这个时候 componentWillReact 会被触发。这使得它很容易追溯渲染并找到导致渲染的操作(action)。

  • componentWillReact 不接收参数
  • componentWillReact 初始化渲染前不会触发 (使用 componentWillMount 替代)
  • componentWillReact 对于 mobx-react@4+, 当接收新的 props 时并在 setState 调用后会触发此钩子
  • 要触发componentWillReact必须在render里面用到被观察的变量
  • 使用Mobx之后不会触发componentWillReceiveProps

CRA配置mobx

  1. 使用CRA创建好项目

    • create-react-app mobx_pro
  2. 在项目中进行配置文件抽离

    • yarn eject
  3. 安装mobx mobx-react

    • yarn add mobx mobx-react

      • mobx 是状态管理工具
      • mobx-react 是做数据分片和数据获取
    • 注意: 如果git冲突
      解决: 我们要原文件先放到本地暂存盘
      git add .
      git commit -m ‘’

      然后 : 安装mobx mobx-react’
      ​注意不要git push

  4. 配置装饰器( 修饰器 es6 ) babel

    • yarn add babel-plugin-transform-decorators-legacy -D
    • yarn add @babel/preset-env -D
    • yarn add babel-plugin-transform-class-properties -D
    • yarn add @babel/plugin-proposal-decorators -D
  5. 配置package.json

       "babel": {
        "plugins": [
          [
            "@babel/plugin-proposal-decorators",
            {
              "legacy": true
            }
          ],
          "transform-class-properties"
        ],
        "presets": [
          "react-app",
          "@babel/preset-env"
        ]
        },
    
     //注意: 以下两个配置顺序不可更改
         [
            "@babel/plugin-proposal-decorators",
            {
              "legacy": true
            }
          ],
          "transform-class-properties"
    

项目中搭建mobx

  1. 新建store目录

    src/store

    ​ home/index.js

    ​ user/index.js

    ​ index.js

  2. 在入口文件中用Provider ---------src/index.js

    import store from './store'
    import { Provider } from 'mobx-react'
    
    ReactDOM.render(
      <Provider store = {store}>
        <Show />
      </Provider>
    , document.getElementById('root'));
    
  3. 使用的组件注入 inject

    import React, { Component } from 'react';
    import { inject,observer } from 'mobx-react'
    @inject('store')
    
    @observer //监听,可以实现数据更新
    class Todo extends Component{
        ...
    }
    export default Todo
    
  4. 打造mobx数据包

    import {
      observable, computed,action
    } from 'mobx'
    class Home {
      @observable  //监听 age
        age = 18
    
      @computed    //当age发生改变时, 自动触发
        get doubleAge(){
          return this.age *= 2
        }
    
      @action  // 用户操作  事件调用
        increment(){
          this.props.store.home.age ++ 
          console.log( this.props.store.home.age )
    
    	//数据请求
           fetch('/data/data.json')
           .then(res => res.json())
           .then( result => console.log( result ))
           .catch( error => console.log( error ))
         }
     }
     const home = new Home()
    
     export default home
    
  5. 打造store/index.js

    import home from './home'
    const store = {
      //实例
      home
    }
    
    export default store
    
  6. 组件内使用数据

    • this.props.store.xxx 可以拿到数据

    • 注意:

      1. this.porps里面没有找到 @action 装饰器定义的方法, 但是可以直接使用,

      2. this会丢失

      observable, computed,action
      } from ‘mobx’
      class Home {
      @observable //监听 age
      age = 18

      @computed //当age发生改变时, 自动触发
      get doubleAge(){
      return this.age *= 2
      }

      @action // 用户操作 事件调用
      increment(){
      this.props.store.home.age ++
      console.log( this.props.store.home.age )

    //数据请求
    fetch(’/data/data.json’)
    .then(res => res.json())
    .then( result => console.log( result ))
    .catch( error => console.log( error ))
    }
    }
    const home = new Home()

    export default home

    
    
  7. 打造store/index.js

    import home from './home'
    const store = {
      //实例
      home
    }
    
    export default store
    
  8. 组件内使用数据

    • this.props.store.xxx 可以拿到数据

    • 注意:

      1. this.porps里面没有找到 @action 装饰器定义的方法, 但是可以直接使用,

      2. this会丢失

        this.props.store.home.increment.bind(this)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值