阅读 40

react-step-by-step之redux详细注释

前言

  • 非常感谢小伙伴的支持和鼓励,从博客园转战到掘金,没想到得到这么多的小伙伴的支持,在此表示十分的感谢,你们鼓励我是写详细注释的教程的巨大动力
  • 今天为大家带来,react-redux的详细教程
    • 没有用脚手架,手动配置webpack各种配置文件,以一个计数器的案例来说明
    • 重要的事说三遍,注释,注释,详细注释
  • react-redux 我就不介绍了,直接上代码(爱都在代码里)

案例搭建

  • 项目目录

  • webpack配置
    const path = require('path')
    const htmlWebpackPlugin = require('html-webpack-plugin')
    
    module.exports = {
      entry: path.join(__dirname, './src/main.js'),//入口配置
      output: {//出口配置
        path: path.join(__dirname, './dist'),
        filename: 'bundle.js'
      },
    
      devServer: {
        open: true,
        port: 3000,
    
        // 添加反向代理的配置
        // https://webpack.js.org/configuration/dev-server/#devserver-proxy
        // https://github.com/chimurai/http-proxy-middleware#http-proxy-options
        // http://www.jianshu.com/p/3bdff821f859
        proxy: {
          // 使用:/api/movie/in_theaters
      // 访问 ‘/api/movie/in_theaters’ ==> 'https://api.douban.com/v2/movie/in_theaters'

      // '/api' 的作用:用来告诉 webpack-dev-server 以 /api 开头的所有请求
      // 都由你来代理
      '/api': {
        // 代理的目标服务器地址
        target: 'https://api.douban.com/v2',
        // https请求需要该设置
        secure: false,
        // 必须设置该项
        changeOrigin: true,
        // '/api/movie/in_theaters' 路径重写为:'/movie/in_theaters'
        // 如果没有该配置,最终的接口地址为:https://api.douban.com/v2/api/movie/in_theaters
        pathRewrite: { '^/api': '' }
      }
    }
  },

  module: {
    rules: [
      { test: /\.css$/, use: ['style-loader', 'css-loader'] },
      { test: /\.(png|jpg|jpeg|gif)$/, use: 'url-loader' },
      // 配置解析 JSX/ES6 语法的loader
      { test: /\.js/, use: 'babel-loader', exclude: /node_modules/ }
    ]
  },

  plugins: [
    new htmlWebpackPlugin({
      template: path.join(__dirname, './src/index.html')
    })
  ]
}
复制代码
  • redux的基本概念,和vue的vuex的思想类似,状态管理。属于换汤不换药
/* 
  redux 状态管理工具

  三个核心概念:

  1 action 动作、行为
    用来描述要执行什么任务,或者说:action提出来了一个想法
    action是一个 JS 对象
    必须得提供一个type属性
    type属性的值采用全大写字母的方式来表示,可以使用下划线来分割多个单词
    const addTodo = {
      type: 'ADD_TODO',
      name: '学习Redux'
    }

    actionCreator: 动作创建器,其实就是一个函数,这个函数返回 action
    const addTodo = (name) => ({
      type: 'ADD_TODO',
      name
    })

    addTodo('学习redux') ===> { type: 'ADD_TODO', name: '学习redux' }
    addTodo('复习 react') ===> { type: 'ADD_TODO', name: '复习 react' }

  2 reducer
    作用: 根据指定的 action 来实现要完成的动态
    语法:(state, action) => newState
    它实际上是一个纯函数
    注意:不要直接修改参数state,而应该根据传入的state(上一个state),来得到最新的状态

  3 store
    在一个 redux 应用中只应该提供一个 store
    store 提供了state; 也提供了操作state的方法
*/

复制代码
  • redux的基本使用
/* 
  redux 的基本使用:
  1 安装: npm i -S redux
  2 导入redux
  3 调用 createStore 方法来创建store
*/

import { createStore } from 'redux'

// 创建reducer
const counter = (state = 0, action) => {
  console.log(state, action)
  switch (action.type) {
    case 'INCREMENT':
      // state++
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

// 创建动作
// 计数器加1的动作:
const increment = () => ({
  type: 'INCREMENT'
})
// 计数器减1的动作:
const decrement = () => ({
  type: 'DECREMENT'
})

// 创建store
const store = createStore(counter)

// 获取状态
console.log('获取状态:', store.getState())

// 分发任务
store.dispatch(increment())
// store.dispatch({ type: 'INCREMENT' })

console.log('新状态为:', store.getState())

复制代码
  • redux中的subscribe
/* 
  redux 的基本使用:
  1 安装: npm i -S redux
  2 导入redux
  3 调用 createStore 方法来创建store
*/

import { createStore } from 'redux'

// 创建reducer
const counter = (state = 0, action) => {
  console.log(state, action)
  switch (action.type) {
    case 'INCREMENT':
      // state++
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

// 创建动作
// 计数器加1的动作:
const increment = () => ({
  type: 'INCREMENT'
})
// 计数器减1的动作:
const decrement = () => ({
  type: 'DECREMENT'
})

// 创建store
const store = createStore(counter)

// 监听store中state的改变:
const unsubscribe = store.subscribe(() => {
  console.log('新状态为:', store.getState())
})

// 获取状态
console.log('获取状态:', store.getState())

// 分发任务
store.dispatch(increment())
// store.dispatch({ type: 'INCREMENT' })

// 分发其他任务:
store.dispatch(increment())

// 在此处调用该方法,表示取消监听state的改变
// 取消后,state再发生改变也不会再被打印内容出来了
unsubscribe()
store.dispatch(increment())

复制代码
  • Counter计数器案例
/* 
  redux 的基本使用:
  1 安装: npm i -S redux
  2 导入redux
  3 调用 createStore 方法来创建store
*/

import { createStore } from 'redux'
import React from 'react'
import ReactDOM from 'react-dom'

// 创建reducer
const counter = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      // state++
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

// 创建动作
// 计数器加1的动作:
const increment = () => ({
  type: 'INCREMENT'
})
// 计数器减1的动作:
const decrement = () => ({
  type: 'DECREMENT'
})

// 创建store
const store = createStore(counter)

store.subscribe(() => {
  // console.log('当前状态:', store.getState())
  render()
})

// 创建组件:
const Counter = () => (
  <div>
    <h1>
      当前值:
      {store.getState()}
    </h1>
    <button onClick={() => store.dispatch(increment())}>+1</button>
    <button onClick={() => store.dispatch(decrement())}>-1</button>
  </div>
)

const render = () => {
  // 渲染组件
  ReactDOM.render(<Counter />, document.getElementById('app'))
}

render()

复制代码
  • class组件forceUpdate更新state
/* 
  redux 的基本使用:
  1 安装: npm i -S redux
  2 导入redux
  3 调用 createStore 方法来创建store
*/

import { createStore } from 'redux'
import React from 'react'
import ReactDOM from 'react-dom'

// 创建reducer
const counter = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      // state++
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

// 创建动作
// 计数器加1的动作:
const increment = () => ({
  type: 'INCREMENT'
})
// 计数器减1的动作:
const decrement = () => ({
  type: 'DECREMENT'
})

// 创建store
const store = createStore(counter)

// 创建组件:
class Counter extends React.Component {
  componentDidMount() {
    // 监听状态的改变
    this.unsubscribe = this.props.store.subscribe(() => {
      // 调用该方法强制组件重新渲染
      this.forceUpdate()
    })
  }

  // 组件卸载时,取消state的监听
  componentWillUnmount() {
    this.unsubscribe()
  }

  render() {
    const { store } = this.props

    return (
      <div>
        <h1>
          当前值:
          {store.getState()}
        </h1>
        <button onClick={() => store.dispatch(increment())}>+1</button>
        <button onClick={() => store.dispatch(decrement())}>-1</button>
      </div>
    )
  }
}

ReactDOM.render(<Counter store={store} />, document.getElementById('app'))

复制代码
  • react-redux包的作用说明(理论知识)
/* 
  redux 的基本使用:
  1 安装: npm i -S redux
  2 导入redux
  3 调用 createStore 方法来创建store
*/

import { createStore } from 'redux'
import React from 'react'
import ReactDOM from 'react-dom'

// 创建reducer
const counter = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      // state++
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

// 创建动作
// 计数器加1的动作:
const increment = () => ({
  type: 'INCREMENT'
})
// 计数器减1的动作:
const decrement = () => ({
  type: 'DECREMENT'
})

// 创建store
const store = createStore(counter)

// 问题:
//
// 使用 class 组件中的 forceUpdate() 方法,不是一个通用的解决方案
// 只能在 class 组件中,而函数组件中,只能使用重新渲染整个应用的方式
// 来将 redux 中state的改变,重新渲染到页面中
//
// 我们应该使用一种更加通用的方式,来将react和redux配合到一起使用

// react-redux 包负责将 react 和 redux 关联到一起使用
// 解决了两个问题:
// 1 react组件如何触发一个action动作
//    也就是如何正确调用 store.dispatch
// 2 store中state改变了,如何将最新的state重新渲染在组件中

// 对于上述第一个问题: react-redux包中提供了一个方法 connect 用来连接react和redux
// 通过 connect 方法,可以包装一个现有的组件(class组件或函数组件),为该组件提供组件
// 内部需要用到的 state 或 操作state的方法
//
// 对于上述第二个问题: 基于react中的单向数据流,父组件提供需要的数据,通过 props 将
// 这些state传递给子组件,子组件中就可以接收到这些数据并展示在页面中了。当父组件中的数据
// 发生改变的时候,因为 单向数据流 的原因,所以,改变后的state又会重新流动到子组件中
// 因此,子组件就接收到了更新后的数据,就会更新组件内容。这样在页面中就看到数据的改变了
// 我们需要使用 react-redux 中提供的父组件(Provider),来包裹我们自己写的组件

// 创建组件:
class Counter extends React.Component {
  componentDidMount() {
    // 监听状态的改变
    this.unsubscribe = this.props.store.subscribe(() => {
      // 调用该方法强制组件重新渲染
      this.forceUpdate()
    })
  }

  // 组件卸载时,取消state的监听
  componentWillUnmount() {
    this.unsubscribe()
  }

  render() {
    const { store } = this.props

    return (
      <div>
        <h1>
          当前值:
          {store.getState()}
        </h1>
        <button onClick={() => store.dispatch(increment())}>+1</button>
        <button onClick={() => store.dispatch(decrement())}>-1</button>
      </div>
    )
  }
}

ReactDOM.render(<Counter store={store} />, document.getElementById('app'))

复制代码
  • react-redux包的基本使用
/* 
  react-redux 的基本使用:
  1 安装:npm i -S react-redux
  2 从 react-redux 包中导入两个内容: Provider 和 connect
  3 使用 Provider 组件包裹整个react应用
  4 使用 connect 包装我们自己的组件,并且传递组件中要使用的 state 和 dispatch 等方法
*/

import { createStore } from 'redux'
import React from 'react'
import ReactDOM from 'react-dom'

// 导入react-redux
import { Provider, connect } from 'react-redux'

// 创建reducer
const counter = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      // state++
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

// 创建动作
// 计数器加1的动作:
const increment = () => ({
  type: 'INCREMENT'
})
// 计数器减1的动作:
const decrement = () => ({
  type: 'DECREMENT'
})

// 创建store
const store = createStore(counter)

store.subscribe(() => {
  console.log('state: ', store.getState())
})

// 问题:
//
// 使用 class 组件中的 forceUpdate() 方法,不是一个通用的解决方案
// 只能在 class 组件中,而函数组件中,只能使用重新渲染整个应用的方式
// 来将 redux 中state的改变,重新渲染到页面中
//
// 我们应该使用一种更加通用的方式,来将react和redux配合到一起使用

// react-redux 包负责将 react 和 redux 关联到一起使用
// 解决了两个问题:
// 1 react组件如何触发一个action动作
//    也就是如何正确调用 store.dispatch
// 2 store中state改变了,如何将最新的state重新渲染在组件中

// 对于上述第一个问题: react-redux包中提供了一个方法 connect 用来连接react和redux
// 通过 connect 方法,可以包装一个现有的组件(class组件或函数组件),为该组件提供组件
// 内部需要用到的 state 或 操作state的方法
//
// 对于上述第二个问题: 基于react中的单向数据流,父组件提供需要的数据,通过 props 将
// 这些state传递给子组件,子组件中就可以接收到这些数据并展示在页面中了。当父组件中的数据
// 发生改变的时候,因为 单向数据流 的原因,所以,改变后的state又会重新流动到子组件中
// 因此,子组件就接收到了更新后的数据,就会更新组件内容。这样在页面中就看到数据的改变了
// 我们需要使用 react-redux 中提供的父组件(Provider),来包裹我们自己写的组件

// 创建组件:
const Counter = props => {
  console.log('Counter 组件props:', props)
  const { dispatch, count } = props
  return (
    <div>
      <h1>
        当前值:
        {count}
      </h1>
      <button onClick={() => dispatch(increment())}>+1</button>
      <button onClick={() => dispatch(decrement())}>-1</button>
    </div>
  )
}

// 为connect方法提供一个参数,用来在组件中获取到redux中提供的数据
// 方法的作用:将 redux 中的state 转化为传递给组件的 props
const mapStateToProps = state => {
  console.log('mapStateToProps:', state)

  // 通过返回值将redux中的state传递给组件

  return {
    // count 属性,就表示:传递给组件 Counter 的数据
    count: state
  }
}

// 如果组件中要用到 redux 中提供的state或dispatch方法,就使用 connect 来包裹这个组件
// 如果组件不需要使用 redux 中提供的任何内容,就不需要使用 connect 包裹了!!!

// 注意: connect() 方法内部又返回了一个新的方法,使用这个返回的方法来包裹 Counter组件
// 调用 connect() 方法后,会返回一个新的组件
// 新返回组件是 原始组件 的包装,JSX结构与原始组件完全相同,只不过包装后,组件中
// 就可以获取到 redux 的 state 以及 dispactch 等方法了
const CounterContainer = connect(mapStateToProps)(Counter)

// 使用 Provider 组件包裹整个应用,并且传递 store 属性给这个组件
// store 属性名是固定的
ReactDOM.render(
  <Provider store={store}>
    <CounterContainer />
  </Provider>,
  document.getElementById('app')
)

复制代码
  • react-redux的使用说明
import { createStore } from 'redux'
import React from 'react'
import ReactDOM from 'react-dom'

// 导入react-redux
import { Provider, connect } from 'react-redux'

// 创建reducer
const counter = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      // state++
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

// 创建动作
// 计数器加1的动作:
const increment = () => ({
  type: 'INCREMENT'
})
// 计数器减1的动作:
const decrement = () => ({
  type: 'DECREMENT'
})

// 创建store
const store = createStore(counter)

store.subscribe(() => {
  console.log('state: ', store.getState())
})

// 创建组件:
const Counter = props => {
  console.log('Counter 组件props:', props)
  const { dispatch, count } = props
  return (
    <div>
      <h1>
        当前值:
        {count}
      </h1>
      <button onClick={() => dispatch(increment())}>+1</button>
      <button onClick={() => dispatch(decrement())}>-1</button>
    </div>
  )
}

// 3 如果要在被包装的组件中,获取到 redux 中的state,需要提供该方法
const mapStateToProps = state => {
  return {
    count: state
  }
}

// 2 使用 connect 方法包装 Counter 组件
//   包装后在 Counter 组件中,就可以通过 props 获取到 redux 中的 state 和 dispatch
const CounterContainer = connect(mapStateToProps)(Counter)

ReactDOM.render(
  // 1 使用Provider组件包裹整个应用
  <Provider store={store}>
    <CounterContainer />
  </Provider>,
  document.getElementById('app')
)

复制代码
  • react-redux中mapDispatchToProps的使用
import { createStore } from 'redux'
import React from 'react'
import ReactDOM from 'react-dom'

// 导入react-redux
import { Provider, connect } from 'react-redux'

// 创建reducer
const counter = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      // state++
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

// 创建动作
// 计数器加1的动作:
const increment = () => ({
  type: 'INCREMENT'
})
// 计数器减1的动作:
const decrement = () => ({
  type: 'DECREMENT'
})

// 创建store
const store = createStore(counter)

store.subscribe(() => {
  console.log('state: ', store.getState())
})

// 创建组件:
const Counter = props => {
  console.log('Counter 组件props:', props)
  const { count, onIncrement, onDecrement } = props
  return (
    <div>
      <h1>
        当前值:
        {count}
      </h1>
      <button onClick={onIncrement}>+1</button>
      <button onClick={onDecrement}>-1</button>
    </div>
  )
}

// 3 如果要在被包装的组件中,获取到 redux 中的state,需要提供该方法
const mapStateToProps = state => {
  return {
    count: state
  }
}

// 4 作用:将 调用dispatch方法的 逻辑代码,通过 props 映射到组件中
//         在组件中,就可以通过 props 获取到传递的方法并使用了
// 说明:如果传递了该方法,在组件中就无法获取到 dispatch !!!
const mapDispatchToProps = dispatch => {
  return {
    onIncrement() {
      dispatch(increment())
    },
    onDecrement() {
      dispatch(decrement())
    }
  }
}

// 2 使用 connect 方法包装 Counter 组件
//   包装后在 Counter 组件中,就可以通过 props 获取到 redux 中的 state 和 dispatch
const CounterContainer = connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter)

ReactDOM.render(
  // 1 使用Provider组件包裹整个应用
  <Provider store={store}>
    <CounterContainer />
  </Provider>,
  document.getElementById('app')
)

复制代码

结语

系列文章

关注下面的标签,发现更多相似文章
评论