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')
)

複製代碼

結語

系列文章

相關文章
相關標籤/搜索