redux

Redux基础



安装
  1. npx create-react-app:安装 react
  2. yarn add redux:生产环境安装
  3. 部分使用:项目部分数据使用 redux 管理。全局使用:项目中所有数据全使用 redux 管理。项目一旦使用 redux 就说明项目数据量一定是大的,推荐全局使用
  4. 注意 React 层获取 state 不是 state.js 内,而是通过订阅-发布从 store 获取,而 store的 state 值是 reducer 提供的

redux


手动撸一个输入添加到列表的东西

目录结构:

src
	store
		index.js			  	创建 store 对象
		actionCreator.js		动作创建者
		reducer.js    			函数,通过动作设置 state
		state.js				state 设定初始值
		type.js					这就是来搞笑的(高大上的)
	page
		todos
			index.css			设置样式
			index.js			对 todos 所有组件一起导出
			TodosContent.js 	组件
			TodosInput.js		组件
1. store/index.js:创建 store 实例
1.从redux模块结构出 createStore 用于创建 store 实例
import { createStore } from 'redux'    
import reducer from './reducer'

2.这里接受一个函数做参数,需要打造 reducer 函数
const store = new createStore( reducer )	
export default store

实例出来的store 对象有 dispatch 和 getState方法。区别 flux

dispatch: ƒ dispatch(action)
getState: ƒ getState()
replaceReducer: ƒ replaceReducer(nextReducer)
subscribe: ƒ subscribe(listener)
Symbol(observable): ƒ observable()


2. store/state.js:设置 state 初始值
const state = {
  todos:[
    {id:1,text:'学习'},
    {id:2,text:'LOL'}
  ]
}
export default state

3. store/reducer.js:纯函数,根据动作改变 state
这里reducer是一个纯函数,接受两个参数。第一个参数是之前的状态,一个是aciton
reducer 函数的返回值是什么,store 的初始值就是什么

import state from './state'
import * as type from './type'

const reducer = (previousState = state, action) => {    这里给previousStare传默认值
  var newState = {
    ...previousState	老师说这是深拷贝,避免直接对 state 操作
  }
  
  switch (action.type) {	通过对 action.type 判断,对 state 做不同处理
    case type.INCREMENT:
      newState.todos.push({
        id: newState.todos.length + 1,
        text: action.payload
      });
      break;
    default: break;
  }

  return newState	返回给 state
}

export default reducer

4. store/actionCreator.js:创造 aciton 对象,提交给 reducer
import * as type from './type'
import store from '.';

const actionCreator ={
  addTodos(val) {
    let action = {
      type: type.INCREMENT,
      payload: val
    }
    
    store.dispatch( action,val )
    
    这里的 dispatch 由 store提供,区别 flux 手动打造
    store.dispatch( action,val )等同于将 payload 写在 action 内
    /* 
    let action ={
      type:type.ADD_TODOS,
      payload:val
    }
    */    
  }
}

export default actionCreator

5. store/type.js
export const INCREMENT = 'INCREMENT'

1. page/todos/TodoInput.js
import React from 'react'
import actionCreator from '../../store/actionCreator'

class TodosInput extends React.Component {
  add = e => {    添加onkeyup的事件处理程序
    if (e.keyCode === 13) {    判断 keyCode 确定回车键
      actionCreator.increment(this.refs.todos_input.value)    触发 actionCreator
      this.refs.todos_input.value = ''    清空输入框
    }
  }

  render() {
    return (
      <div className="TodosContent">
        <input
          type="text"
          placeholder="请输入待办事项"
          
          通过 ref 绑定 DOM,这里 ref 也可以接受一个回调函数 
          // ref={ el => this.todosinput = el }
          ref='todos_input'
          
          onKeyUp={this.add}    绑定事件
        />
      </div>
    )
  }
}

export default TodosInput

2. page/todos/TodoInput.js
import React from 'react'
import store from '../../store'

function Item(props) {    创建 Item 无状态组件
  return (
    <li>{props.item.text}</li>    通过 props 设置 li 标签内容
  )
}

class TodosContent extends React.Component {
  constructor() {
    super()
    this.state = {
      todos: store.getState().todos    通过 store.getState() 获取 state 值
    }
  }

  renderItem = () => {    设置一个方法,返回一个结构
    return this.state.todos.map(item => {    遍历状态 获取结构,
      return (
        <Item item={item} key={item.id} />    使用无状态组件,分离结构和逻辑,这里直接写<li></li>也是可以
      )
    })
  }

  render() {
    return (
      <div className="TodosContent">
        {this.renderItem()}    调用方法获取结构
      </div>
    )
  }

  componentWillMount() {    通过钩子订阅事件
  
    store.subscribe(() => {   事件订阅,当 state 改变,需要用这个更新状态
     
    状态不会自动更新,上边 constructor 内 this 是写死的。
    需要重新对状态修改,状态的修改不能直接赋值,需要 this.setState() 方法,接受一个对象即新状态
      this.setState({
        todos: store.getState().todos
      })
    })
  }
}
export default TodosContent

3. page/todos/index.js
import TodosContent from './TodosContent'
import TodosInput from './TodosInput'
import './index.css'

export { TodosContent, TodosInput }


Redux 进阶

1. combineReducers 数据模块划分

Redux 数据模块划分
在实际中,我们对数据分块。一个项目中存在很多数据,我们不能都放在 state 中,这样导致混乱。我们将整个数据划分,通过中间件我们可以统一管理不同的 reducer 。因为reducer 是真正管理数据的地方。

store目录结构

store
	home
		// index.js    不需要 indexed.js 只需要一个总体的 store 
		type.js
		state.js
		reducer.js
		acttionCreators.js
		
	todos
		type.js
		state.js
		reducer.js
		acttionCreators.js
		
	mine
	...
	index.js
	//state.js	不需要总体的 state 分片自己的 state 
	reducer.js
	
		import { combineReducers } from 'redux'
		import home from './home/reducer'
		const reducer = combineReducers({ home })
		export default reducer
		
	acttionCreators.js

2. react-redux
  1. 依赖:yarn add redux
  2. 安装:yarn add react-redux
  3. 在根组件最外层嵌套 Provider 组件,并将 store 当作属性传给 Provider
  4. 利用 connect 方法使用 store 相关 api
  5. 作用:简化操作流程,相当于 vuex 的 map什么鬼,忘了

在使用 redux 中,我们发现有几个动作老师重复的,组件想使用 store 数据,必须在constructor 中利用 store.getState() 获取在挂载在自己状态上。我们要通过 actionCreator 中请求数据,然后发送给前台。
组件想要更新状态需要在 componentWillMount(){} 钩子中利用 store.subscribe() 方法注册一个监听函数,然后获取最新状态后再使用 this.setState() 方法更新自己的状态。如此繁琐的步骤我选择放弃~~
我们引入 react-redux 工具来处理这些问题
react-redux 只是一个辅助工具,将 react 和redux 更好的连接。如果一个组件想要使用store中的数据或者actionCreator的方法,我们都应该其变化为容器组件包裹UI组件的样子。其中,容器组件负责拦截 store,将状态、方法传递给 UI 组件,UI 组件从属性上获取通过 api 后直接使用。
核心API
Provider:传递数据
connect:生成容器组件

容器组件、UI 组件
展示组件 容器组件
作用 描述如何展示(骨架、样式) 描述如何运行(数据获取,状态更新)
直接使用Redux
数据来源 props 监听 Redux state
数据修改 从 props调用回调函数 向Redux 派发 action
调用方式 手动 根据connet生成ui组件 由 React Redux 自动生成

1.代码重复
组件内状态获取
事件订阅
action发送
解决:使用
2.异步
如果异步,则上述解决办法失效,还用重新写
解决:使用中间件

使用

src/index.js

react-redux 帮我们 获取 store 并且帮我们订阅(响应式)

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

ReactDOM.render(
  <Provider store={store}>    	包裹根组件,传递 store
    <App />
  </Provider>
  , document.getElementById('root'));

组件.js

import React from 'react'

import { connect } from 'react-redux'
import { bindActionCreators } from 'redux';
import actionCreator from '../../store/actionCreator'

class Mine extends React.Component{
  add=()=>{
    actionCreator.add()
  }
  render(){
    console.log(this.props.dispatch)
    return (
      <div>
        <h3>Mine</h3>
        <p> count:{this.props.count} </p>
        <button onClick={this.add}>+</button>
        <p> name:{this.props.name} </p>
      </div>
    )
  }
}

监听 Redux store 的变化。只要 Redux store 发生改变,mapStateToProps 函数就会被调用。
该回调函数必须返回一个纯对象,这个对象会与组件的 props 合并。

const mapStateToProps = (state)=>{

  如果 state 只有一层,数据直接在 state 内
  return state
  
  如果数据有两次:state={ mine:{name:'zhangsan',count:1} }
  return {
    mine:{...state.mine}
  }
}

将一些能用到 dispatch 的组件传递过去,并且发送动作由这里完成
const mapDispatchToProps = (dispatch)=>{
  return bindActionCreators(actionCreator,dispatch)
}

export default connect(mapStateToProps,mapDispatchToProps)(Mine)

actionCreator.js

const actionCreator ={

1.这么写
  increment(){
    let action = {
      type:'INCREMENT'
    }
    return action
  }
  
2.或者直接这么写也可以
  increment(val){
 	return {
	 type:type.INCREMENT,
	 payload:val
	}
  }
  
}
 
export default actionCreator

3. redux-thunk 异步请求
  1. 安装 yarn add redux-thunk
  2. 创建 store 时使用中间件

action 只是一个对象,不被期望包含异步操作,存在难以维护,业务逻辑不清晰。
使用中间件后,可以解决上述问题,常见的 actionCreator 异步库:

Redux-thunk(主流)
Redux-saga
Redux-effects
Redux-loop

基于 promise 的异步库:
Redux-promise
Redux-promises

如果没有异步工具,我们异步获取数据会是下面这样的

组件.js

import React from 'react'
import actionCreator from '../../store/home/actionCreator'
import store from '../../store'

class Name extends React.Component{
  constructor(){
    super()
    this.state={
      name:''
    }
  }
  
  1.这里是老师上课讲的关于异步获取步骤,我觉得不行
  async componentDidMount(){
  	这里调用动作发送请求获取和修改数据,异步
    await actionCreator.getName()
    这里订阅获取数据
    store.subscribe(()=>{
      这里对自己的状态修改,同步。所以要等异步处理完成后在修改自身状态
      this.setState({
        name:store.getState().home.name
      })
    })
  }
  
  2.这是我自己测试的,测试成功。不需要 asyncawait 本身事件订阅接受的就是个回调函数
  在组件挂在前发送数据,然后订阅如果
  componentDidMount(){
    actionCreator.getName()
    store.subscribe(()=>{
      this.setState({
        name:store.getState().home.name
      })
    })
  }

  render(){
    return(
      <div>
        <h1> redux 异步请求 </h1>
        <button>获取</button>
        <p>Name:{this.state.name.name}</p>
      </div>
    )
  }
}

export default Name

actionCreatore.js

import store from "../";

const actionCreator = {
  getName() {
    fetch('/data.json')
      .then(res => res.json())
      .then(data => {
        let action = {
          type: 'GETNAME',
          payload: data
        }
        store.dispatch(action)
      })
      .catch(err => console.log(err))
  }
}
export default actionCreator
使用 异步处理
improt {createState, applyMiddleware } from 'redux'

imorpt thunk from 'redux-thunk'

imorpt reducer from './reducer'

const store = new createStore( reducer, applyMiddleware(thunk) )

export default store

actionCreator.js


redux