05_redux学习

Redux学习笔记

文章目录

1. redux理解

1.1 学习文档

  1. 英文文档: https://redux.js.org/

  2. 中文文档: http://www.redux.org.cn/

Github: https://github.com/reactjs/redux

1.2 redux是什么

  1. redux是一个独立专门用于做状态管理的JS库(不是react插件库)

  2. 它可以用在react, angular, vue等项目中, 但基本与react配合使用

  3. 作用: 集中式管理react应用中多个组件共享的状态

1.3 redux工作流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3jPWx1oO-1588482452702)(redux工作流程.png)]

1.4 什么情况下需要使用redux

  1. 总体原则: 能不用就不用, 如果不用比较吃力才考虑使用 一般都要用

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

2. redux的核心API

2.1. createStore()

  1. 作用:

​ 创建包含指定reducer的store对象

  1. 编码:
import {createStore} from 'redux'
import counter from './reducers/counter'

const store = createStore(counter)

2.2 store对象

  1. 作用: redux库最核心的管理对象

  2. 它内部维护着:

​ state

​ reducer

  1. 核心方法:

​ getState()

​ dispatch(action)

​ subscribe(listener)

  1. 编码:
store.getState()
store.dispatch({type:'INCREMENT', number})
store.subscribe(render)

2.3 applyMiddleware()

  1. 作用: 应用上基于redux的中间件(插件库)

  2. 编码:

import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'  // redux异步中间件
const store = createStore(
  counter,
  applyMiddleware(thunk) // 应用上异步中间件
)

2.4. combineReducers()

  1. 作用: 合并多个reducer函数

  2. 编码:

export default combineReducers({
  user,
  chatUser,
  chat
})

3. redux的三个核心概念

3.1 action

  1. 标识要执行行为的对象

  2. 包含2个方面的属性

​ a. type: 标识属性, 值为字符串, 唯一, 必要属性

​ b. xxx: 数据属性, 值类型任意, 可选属性

  1. 例子:
const action = {
    type: 'INCREMENT',
    data: 2
}
  1. Action Creator(创建Action的工厂函数)

​ const increment = (number) => ({type: ‘INCREMENT’, data: number})

3.2 reducer

  1. 根据老的state和action, 产生新的state的纯函数

  2. 样例

 export default function counter(state = 0, action) {
 switch (action.type) {
 case 'INCREMENT':
    return state + action.data
   case 'DECREMENT':
    return state - action.data
   default:
    return state
  }
}
  1. 注意

​ a. 返回一个新的状态

​ b. 不要修改原来的状态

3.3. store

  1. 将state,action与reducer联系在一起的对象

  2. 如何得到此对象?

​ import {createStore} from ‘redux’

​ import reducer from ‘./reducers’

​ const store = createStore(reducer)

  1. 此对象的功能?

​ getState(): 得到state

​ dispatch(action): 分发action, 触发reducer调用, 产生新的state

​ ubscribe(listener): 注册监听, 当产生了新的state时, 自动调用

4. 使用redux编写应用

4.1 下载依赖包

npm install --save redux

4.2 redux/action-types.js

/*
* 包含所有action.type的常量字符串
* */

export const  INCREMENT = 'INCREMENT'
export const  DECREMENT = 'DECREMENT'

4.3 redux/actions.js

/*
* 包含所有 action creater*/
import {INCREMENT,DECREMENT} from './action-types'

//增加
export const increment = (number) => ({
  type:INCREMENT,data:number
})

//减少
export const decrement = (number) => ({
  type:DECREMENT,data:number
})

4.5. redux/reducers.js

/*
* 包含n个reducer的模块
* */
import {INCREMENT, DECREMENT} from './action-types'

export function counter(state = 0, action) {
  console.log('counter()', state, action)
  switch (action.type) {
    case INCREMENT:
      return state + action.data
    case DECREMENT:
      return state - action.data
    default:
      return state
  }
}

4.6 components/app.jsx

import React, {Component} from "react";

import * as actions from '../redux/actions'
//或者 import {increment,decrement} from '../redux/actions'

export default class App extends Component { //默认暴露,



  incremnet = () => {
    //  1.得到选择增加数量
    const number = this.choose.value * 1
    //  2.调用store的方法更新状态
    this.props.store.dispatch(actions.increment(number))
  }

  decrement = () => {
    //  1.得到选择减少数量
    const number = this.choose.value * 1
    //  2.调用store的方法更新状态
    this.props.store.dispatch(actions.decrement(number))
  }

  incrementIfOdd = () => {
    //  1.得到选择减少数量
    const number = this.choose.value * 1
    //  2.得到原本的count状态
    const count = this.props.store.getState()
    //  3.判断,满足条件才更新
    if (count % 2 === 1) {
      //  4.更新state
      this.props.store.dispatch(actions.increment(number))
    }
  }

  incrementAsync = () => {
    //  1.得到选择减少数量
    const number = this.choose.value * 1
    //  启动延时定时器
    setTimeout(()=>{
      //  2.更新state
      this.props.store.dispatch(actions.increment(number))
    },1000)
  }

  render() {
    const count = this.props.store.getState()
    return (
      <div>
        <p>click {count} times</p>
        <div>
          <select ref={select => this.choose = select}>
            <option value="1">1</option>
            <option value="2">2</option>
            <option value="3">3</option>
          </select>&nbsp;
          <button onClick={this.incremnet}>+</button>
          <button onClick={this.decrement}>-</button>
          <button onClick={this.incrementIfOdd}>increment if odd</button>
          <button onClick={this.incrementAsync}>increment async</button>
        </div>
      </div>
    )
  }
}

4.7 index.js

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

import App from './components/app';
import store from "./redux/store";
import './index.css';

function render() {
  ReactDOM.render(<App store={store}/>, document.getElementById('root'));
}
//初始化渲染
render()
//订阅监听(store中状态变化了,就会自动调用进行重绘)
store.subscribe(render)

4.8 redux/store.js

import {createStore} from 'redux'
import {counter} from './redux/reducer'

//生成store对象
const store = createStore(counter)//内部会第一次调用reducer函数,得到初始值0
console.log(store)

//暴露出去
export default store

4.9. 问题

  1. redux与react组件的代码耦合度太高

  2. 编码不够简洁

由此需要引入react-redux插件开启第5节

5. react-redux

5.1. 理解

  1. 一个react插件库

  2. 专门用来简化react应用中使用redux

5.2. React-Redux将所有组件分成两大类

5.2.1 UI组件

​ a. 只负责 UI 的呈现,不带有任何业务逻辑

​ b. 通过props接收数据(一般数据和函数)

​ c. 不使用任何 Redux 的 API

​ d. 一般保存在components文件夹下

5.2.2 容器组件

​ a. 负责管理数据和业务逻辑,不负责UI的呈现

​ b. 使用 Redux 的 API

​ c. 一般保存在containers文件夹下

5.3. 相关API

5.3.1 Provider标签

让所有组件都可以得到state数据

<Provider store={store}>
<App />
</Provider>

5.3.2 connect()

用于包装 UI 组件生成容器组件

import { connect } from 'react-redux'
  connect(
   mapStateToprops,
   mapDispatchToProps
  )(Counter)
5.3.3 mapStateToprops()

将外部的数据(即state对象)转换为UI组件的标签属性

  const mapStateToprops = function (state) {
  return {
   value: state
  }
  }
5.3.4 mapDispatchToProps()

将分发action的函数转换为UI组件的标签属性

简洁语法可以直接指定为actions对象或包含多个action方法的对象

5.4. 使用react-redux

5.4.1 下载依赖包

npm install --save react-redux

5.4.2 redux/action-types.js 不变
5.4.3 redux/actions.js 不变
5.4.4 redux/reducers.js 不变
5.4.5 components/counter.jsx
import React, {Component} from "react";
import PropTypes from 'prop-types'



export default class Counter extends Component { //默认暴露,

  static propTypes={
    count:PropTypes.number.isRequired,
    increment:PropTypes.func.isRequired,
    decrement:PropTypes.func.isRequired
  }


  incremnet = () => {
    //  1.得到选择增加数量
    const number = this.choose.value * 1
    //  2.调用store的方法更新状态
    this.props.increment(number)
  }

  decrement = () => {
    //  1.得到选择减少数量
    const number = this.choose.value * 1
    //  2.调用store的方法更新状态
    this.props.decrement(number)
  }

  incrementIfOdd = () => {
    //  1.得到选择减少数量
    const number = this.choose.value * 1
    //  2.得到原本的count状态
    const count = this.props.count
    //  3.判断,满足条件才更新
    if (count % 2 === 1) {
      //  4.更新state
      this.props.increment(number)
    }
  }

  incrementAsync = () => {
    //  1.得到选择减少数量
    const number = this.choose.value * 1
    //  启动延时定时器
    setTimeout(()=>{
      //  2.更新state
      this.props.increment(number)
    },1000)
  }

  render() {
    const {count} = this.props
    return (
      <div>
        <p>click {count} times</p>
        <div>
          <select ref={select => this.choose = select}>
            <option value="1">1</option>
            <option value="2">2</option>
            <option value="3">3</option>
          </select>&nbsp;
          <button onClick={this.incremnet}>+</button>
          <button onClick={this.decrement}>-</button>
          <button onClick={this.incrementIfOdd}>increment if odd</button>
          <button onClick={this.incrementAsync}>increment async</button>
        </div>
      </div>
    )
  }
}
5.4.6 containters/app.jsx
import React, {Component} from "react";
import {connect} from 'react-redux'
//import * as actions from '../redux/actions'
import {increment,decrement} from '../redux/actions'
import Counter from '../components/counter'

export default connect(
  state =>({count:state}),
  {increment,decrement}
)(Counter)
5.4.7 index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux'

import App from './containers/app';
import store from "./redux/store";
import './index.css';


  ReactDOM.render((
    <Provider store={store}>
      <App />
    </Provider>
    ), document.getElementById('root'));

5.5. 问题

  1. redux默认是不能进行异步处理的,

  2. 应用中又需要在redux中执行异步任务(如ajax, 定时器)

由此需要引入异步中间插件开启第6节

6. redux异步编程

6.1 下载redux插件(异步中间件)

​ npm install --save redux-thunk

6.2 redux/actions.js

/*
* 包含所有 action creater
* 同步的action返回的都是一个对象
* 异步的action返回的是一个函数
* */

import {INCREMENT, DECREMENT} from './action-types'

//增加
export const increment = (number) => ({
  type: INCREMENT, data: number
})

//减少
export const decrement = (number) => ({
  type: DECREMENT, data: number
})

//异步action 只有用了中间件才能返回函数,否则只能默认返回对象
export const incrementAsync = (number) => {
  return dispatch => {
    //异步的代码
    setTimeout(() => {
      //1s后分发一个增加的action
      dispatch(increment(number))
    }, 1000)
  }
}

6.3 components/counter.jsx

  static propTypes={
    count:PropTypes.number.isRequired,
    increment:PropTypes.func.isRequired,
    decrement:PropTypes.func.isRequired,
    incrementAsync:PropTypes.func.isRequired
  }
incrementAsync = () => {
  //  1.得到选择减少数量
  const number = this.choose.value * 1
  this.props.incrementAsync(number)
}

6.4 containers/app.jsx

import {increment,decrement,incrementAsync} from '../redux/actions'


export default connect(
  state =>({count:state}),
  //(actions.increment, actions.decrement)
  {increment,decrement,incrementAsync}
)(Counter)

6.5 redux/store.js

mport {createStore,applyMiddleware} from 'redux'
import thunk from 'redux-thunk'


import {counter} from './reducer'
//生成store对象,
const store = createStore(
  counter,
  applyMiddleware(thunk) // 应用上异步中间件
)//内部会第一次调用reducer函数,得到初始值0
//console.log(store)

//暴露出去
export default store

7. 使用redux调试工具

7.1. 安装chrome浏览器插件

7.2. 下载工具依赖包

npm install --save-dev redux-devtools-extension

7.3. 编码

import {createStore,applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'

import {counter} from './reducer'
//生成store对象,
const store = createStore(
  counter,
  composeWithDevTools(applyMiddleware(thunk)) // 应用上异步中间件
)//内部会第一次调用reducer函数,得到初始值0
//console.log(store)

//暴露出去
export default store

8. 相关重要知识: 纯函数和高阶函数

8.1 纯函数

  1. 一类特别的函数: 只要是同样的输入,必定得到同样的输出

  2. 必须遵守以下一些约束

a. 不得改写参数

b. 不能调用系统 I/O 的API

c. 能调用Date.now()或者Math.random()等不纯的方法

  1. reducer函数必须是一个纯函数

8.2 高阶函数

  1. 理解: 一类特别的函数

a. 情况1: 参数是函数

b. 情况2: 返回是函数

  1.    常见的高阶函数: 
    

a. 定时器设置函数

b. 数组的map()/filter()/reduce()/find()/bind()

c. react-redux中的connect函数

  1.    作用: 
    

a. 能实现更加动态, 更加可扩展的功能

9.combineReducers 管理多个reducer函数

reduer.js

import {combineReducers} from 'redux'

import {
  ADD_COMMENT,
  DELETE_COMMENT,
  RECEIVE_COMMENTS
} from './action-types'

const initComments = []

//函数1
function comments(state = initComments, action) {
  switch (action.type) {
    case ADD_COMMENT:
      return [...state, action.data]
    case DELETE_COMMENT:
      return state.filter((c, index) => index !== action.data)
    case RECEIVE_COMMENTS:
      return action.data
    default:
      return state
  }
}

//函数2
function counter(state = 0, action) {
  console.log('counter()', state, action)
  switch (action.type) {
    case INCREMENT:
      return state + action.data
    case DECREMENT:
      return state - action.data
    default:
      return state
  }
}

export default combineReducers({
  comments,couner
})

store.js

import reducers from './reducer'

export default createStore(
  reducers, composeWithDevTools(applyMiddleware(thunk))
)

app.jsx

export default connect(
  state=>({comments:state.comments}),//state就是一个comments数组,因为state已经有了两个状态了
  {addComment,deleteComment,getComments}
)(App)

10. redux改编comments项目

10.1 components/comment-add.jsx

import React, {Component} from "react";
import PropTypes from 'prop-types'

export default class CommentAdd extends Component{ //默认暴露,

  static propTypes = {
    addComment:PropTypes.func.isRequired
  }

  state = {
    username:'',
    content:''
  }

//一直bind过于麻烦,可以使用箭头函数,现在箭头函数没有自己的this,就看外围this正好是组件对象
  handleSubmit = () =>{
    //收集数据,并封装成comment对象
    const comment = this.state
    //更新状态
    this.props.addComment(comment)
    //清除输入数据
    this.setState({
        username:'',
        content:''
      }
    )
  }

  handleNameChange = (event) =>{
    const username = event.target.value
    this.setState({username})
  }

  handleContentChange = (event) =>{
    const content = event.target.value
    this.setState({content})
  }

  render() {

    const {username,content} = this.state

    return (
      <div className="col-md-4" >
        <form className="form-horizontal">
          <div className="form-group">
            <label>用户名</label>
            <input type="text" className="form-control" placeholder="用户名"
                   value={username} onChange={this.handleNameChange}/>
          </div>
          <div className="form-group">
            <label>评论内容</label>
            <textarea className="form-control" rows="6" placeholder="评论内容"
                   value={content} onChange={this.handleContentChange}></textarea>
          </div>
          <div className="form-group">
            <div className="col-sm-offset-2 col-sm-10">
              <button type="button" className="btn btn-default pull-right"
                      onClick={this.handleSubmit}>提交</button>
            </div>
          </div>
        </form>
      </div>
    )
  }
}

10.2 components/comment-item.jsx

import React, {Component} from "react";
import PropTypes from 'prop-types'
import {connect} from 'react-redux'

import './commentItem.css'
import {deleteComment} from '../../redux/actions'

class CommentItem extends Component { //默认暴露,

  static propTypes = {
    comment: PropTypes.object.isRequired,
    index: PropTypes.number.isRequired,
    deleteComment: PropTypes.func.isRequired
  }

  handleClick = () => {
    const {comment, index, deleteComment} = this.props
    //提示
    if (window.confirm(`确定删除${comment.username}的评论吗?`)) {
      deleteComment(index)
    }
  }

  render() {
    //let comment = this.props.comment
    const {comment} = this.props
    //alert({comment}.length)
    //alert(Object.keys({comment}).length)
    //console.log(Object.keys({comment}))
    return (
      <li className="list-group-item">
        <div className="handle">
          <a href="javascript:;" onClick={this.handleClick}>删除</a>
        </div>
        <p className="user"><span>{comment.username}</span><span>说:</span></p>
        <p className="centence">{comment.content}</p>
      </li>
    )
  }
}
//因为这里要用到删除方法,所以要用connect函数传入方法
export default connect(
  null,
  {deleteComment}
)(CommentItem)

10.3 components/comment-list.jsx

import React, {Component} from "react";
//这个包需要下载,命令 npm install --save prop-types
import PropTypes from 'prop-types'

import CommentItem from "../comment-item/comment-item";
import './commentList.css'

export default class CommentList extends Component{ //默认暴露,

  //不加static是给组件对象添加,加上是给组件类CommentList指定属性
  static propTypes = {
    comments: PropTypes.array.isRequired,
    //delete: PropTypes.func.isRequired
  }

render() {
    const {comments,deleteComment} = this.props
  //let comments = this.props.comments
  //计算出是否显示
  const display = comments.length === 0 ? 'block' : 'none'
  return (
    <div className="col-md-8">
      <h3 className="reply">评论回复:</h3>
      <h2 style={{display}}>暂无评论,点击左侧添加评论!!!</h2>
      <ul className="list-group">
        {
          comments.map((comment,index)=><CommentItem comment={comment} key={index}
                                     index={index}  />)
        }
      </ul>
    </div>
  )
}
}

10.4 container/app.jsx

import React, {Component} from "react";
import {PropTypes} from 'prop-types'
import {connect} from  'react-redux'

import CommentAdd from "../../components/comment-add/comment-add";
import CommentList from "../../components/comment-list/comment-list";
import {addComment,deleteComment,getComments} from "../../redux/actions";

class App extends Component { //默认暴露,

  static propTypes = {
    comments:PropTypes.array.isRequired,
    addComment:PropTypes.func.isRequired,
    deleteComment:PropTypes.func.isRequired,
    getComments:PropTypes.func.isRequired
  }

  componentDidMount() {
    this.props.getComments()
  }

  render() {

    const {comments,addComment,deleteComment} = this.props

    return (
      <div id="app">
        <div>
          <header className="site-header jumbotron">
            <div className="container">
              <div className="row">
                <div className="col-xs-12">
                  <h1>请发表对React的评论</h1>
                </div>
              </div>
            </div>
          </header>
          <div className="container">
            <CommentAdd addComment ={addComment}/>
            <CommentList comments={comments} deleteComment={deleteComment}/>
          </div>
        </div>
      </div>
    )
  }
}

export default connect(
  state=>({comments:state.comments}),//state就是一个comments数组
  {addComment,deleteComment,getComments}
)(App)

10.5 redux/action-types.js

/*
*  actions的type常量
* */
export const ADD_COMMENT = 'add_comment'
export const DELETE_COMMENT = 'delete_comment' //大小写快捷键转换 ctrl+shift+x
export const RECEIVE_COMMENTS = 'receive_comments'

10.6 redux/actions.js

/*
* 包含所有工厂函数*/
import {ADD_COMMENT, DELETE_COMMENT, RECEIVE_COMMENTS} from './action-types'

export const addComment = (comment) => ({
  type: ADD_COMMENT, data: comment
})

export const deleteComment = (index) => ({
  type: DELETE_COMMENT, data: index
})

const receiveComments = (comments) => ({
  type: RECEIVE_COMMENTS, data: comments
})

export const getComments = () => {
  return dispatch => {
    setTimeout(() => {
      const comments = [
        {username: 'Tom', content: 'React挺好的!'},
        {username: 'Mary', content: 'React太难了!'}
      ]
      dispatch(receiveComments((comments)))
    },1000)
  }
}

10.7 redux/reducer.js

/*
* 包含n个reduer函数,根据老的state和action返回新的state*/
import {combineReducers} from 'redux'
import {ADD_COMMENT, DELETE_COMMENT,RECEIVE_COMMENTS} from './action-types'

const initComments = [
/*   {username: 'Tom', content: 'React挺好的!'},
   {username: 'Mary', content: 'React太难了!'}*/
]

function comments(state = initComments, action) {
  switch (action.type) {
    case ADD_COMMENT:
      return [action.data, ...state]
    case DELETE_COMMENT:
      /*
        filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
        注意: filter() 不会对空数组进行检测。
        注意: filter() 不会改变原始数组。
      */
      return state.filter((c, index) => index !== action.data)
    case RECEIVE_COMMENTS:
      return action.data
    default:
      return state
  }
}

//函数2
function counter(state = 0, action) {
  console.log('counter()', state, action)
  switch (action.type) {
    case 1:
      return state + action.data
    case 2:
      return state - action.data
    default:
      return state
  }
}

export default combineReducers({
  comments,counter
})

//redux向外暴露的是一个什么结构?
//是一个对象,有两个属性:counter:数值属性,comments:数组

10.8 redux/store.js

/*
* redux最核心的管理对象store*/
import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import {composeWithDevTools} from 'redux-devtools-extension'

import reducers from './reducer'

export default createStore(
  reducers, composeWithDevTools(applyMiddleware(thunk))
)

10.9 index.js

import React from "react";
import ReactDOM from "react-dom"
import {Provider} from 'react-redux'

import App from './container/app/app'
import store from './redux/store'


ReactDOM.render((
  <Provider store={store}>
    <App/>
  </Provider>
), document.getElementById('root'))
eturn action.data
    default:
      return state
  }
}

//函数2
function counter(state = 0, action) {
  console.log('counter()', state, action)
  switch (action.type) {
    case 1:
      return state + action.data
    case 2:
      return state - action.data
    default:
      return state
  }
}

export default combineReducers({
  comments,counter
})

//redux向外暴露的是一个什么结构?
//是一个对象,有两个属性:counter:数值属性,comments:数组

10.8 redux/store.js

/*
* redux最核心的管理对象store*/
import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import {composeWithDevTools} from 'redux-devtools-extension'

import reducers from './reducer'

export default createStore(
  reducers, composeWithDevTools(applyMiddleware(thunk))
)

10.9 index.js

import React from "react";
import ReactDOM from "react-dom"
import {Provider} from 'react-redux'

import App from './container/app/app'
import store from './redux/store'


ReactDOM.render((
  <Provider store={store}>
    <App/>
  </Provider>
), document.getElementById('root'))
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值