前端数据流之Redux篇(知识点)

redux中文文档

适用场景

UI层简单,没有什么互动的,不需要redux

使用方式复杂,与服务器大量交互,视图层要从多个来源获取数据的,使用redux
比如说:

  • 组件状态要功响
  • 一个组件更改全局状态
  • 一个组件需要改变另一个组件的状态

设计思想

web应用是个状态机,视图与状态一一对应
所有的状态,保存在一个对象里。

基本使用

npm i -g create-react-app安装包
create-react-app redux-api创建项目
npm i redux安装redux

index.js

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

// 1.定义reducer
// state:容器的初始状态
// action:修改state的行为类型
//        type - 行为类型; payload(可选的)其他数据
function Reducer(state = 0, action) {
  const {type} = action
  if (type === 'INCREMENT') {
    return state + 1
  } else if (type === 'DECREMENT') {
    return state - 1
  } else {
    return state
  }
}
// 2.创建store
const store = createStore(Reducer, 110)
// 3.获取状态
console.log(store.getState())
// 4.更新状态
// store.dispatch({type:行 为类型,额外参数})
// 发起dispatch就是在调用reducer,dispatch接收的参数叫action
setTimeout(() => {
  store.dispatch({
    type: 'INCREMENT'
  })
}, 1000);
// 5.检测状态变化,驱动视图更新
store.subscribe(() => {
  console.log(store.getState())
  render()
})

const Counter = (props) => {
  return <div>
    <h1>{props.value}</h1>
    <button>Increment</button>
    <button>Decrement</button>
  </div>
}

function render() {
  ReactDOM.render(<Counter value={store.getState()}></Counter>, document.getElementById('root'));
}

render()

核心概念

  • store
    保存数据的容器,通过createStore来生成。
import { createStore } from 'redux'
const store = createStore(Reducer, 110)
  • state
    包含所有的数据。
    一个state对应一个view
const state = store.getState()
  • action
    view发出action,通知state进行改变。
    action是一个对象,type属性必须有,表示名称。其他属性自由设置。
    action是改变state的唯一方法。
const action = {
  type: 'INCREMENT',
  payload: 'learn redux'
}
  • action creator
    生成action的函数
const ADD_TODO = '添加todo'
function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}
const action = addTodo('learn redux')
  • store.dispatch
    view通过此方法发出action。也是唯一的方法。
    store.dispatch接受action对象作为参数,然后将其发送出去。
  store.dispatch({
    type: 'INCREMENT',
    payload: 'learn redux'
  })
store.dispatch(addTodo('learn redux'))
  • reducer
    state的计算过程。接受action和当前state作为参数,返回新的state
function Reducer(state = 0, action) {
  const {type} = action
  if (type === 'INCREMENT') {
    return state + 1
  } else if (type === 'DECREMENT') {
    return state - 1
  } else {
    return state
  }
}

一般情况下,store.dispatch方法会触发reducer的自动执行,所以store生成时,需要传入reducer

const store = createStore(Reducer, 110)

reducer是纯函数,同样的输入,必定得到同样的输出。
reducer函数里的state不能改变,必须返回一个全新的对象。最好把state设置为只读。

// state是对象
function reducer(state, action) {
  return { ...state, ...newState}
}
// state是数组
function reducer(state, action) {
  return [ ...state, newItem]
}
  • store.subscribe()
    监听方法,state改变,就会立即执行。
    view更新的函数放入其中,就会实现view的自动渲染。
store.subscribe(() => {
  console.log(store.getState())
  render()
})

如果store.subscribe方法返回函数,调用这个函数,即可解除监听

let unsubscribe = store.subscribe(() => {
  connsole.log(store.getState())
})
unsubscribe() // 解除监听

reducer的拆分

项目中state一般很庞大,所以需要对reducer进行拆分

拆分前的代码:

const Reducer = (state = {}, action = {}) => {
  const { type, payload } = action
  switch (type) {
    case 'ADD_CHAT':
      return Object.assign({}, state, {
        charLog: state.charLog.concat(payload)
      })
    case 'CHANGE_STATUS':
      return Object.assign({}, state, {
        statusMessage: payload
      })
    case 'CHANGE_USERNAME':
      return Object.assign({}, state, {
        userName: payload
      })
    default: return state
  }
}

拆分后的文件结构


chatLog.js

export default function chatLog (state = [], action) {
  const { type, payload } = action
  switch (type) {
    case 'ADD_CHAT':
      return [...state, payload]
    default: return state
  }
}

statusMessage.js

export default function statusMessage (state = '', action) {
  const { type, payload } = action
  switch (type) {
    case 'CHANGE_STATUS':
      return payload
    default: return state
  }
}

userName.js

export default function userName (state = '', action) {
  const { type, payload } = action
  switch (type) {
    case 'CHANGE_USERNAME':
      return payload
    default: return state
  }
}

index.js

import chatLog from './chatLog'
import statusMessage from './statusMessage'
import userNameChange from './userName'
import { combineReducers } from 'redux'

// 写法1:不推荐
// export default function (state = {}, action = {}) {
//   return {
//     chatLog: chatLog(state.chatLog, action),
//     statusMessage: statusMessage(state.statusMessage, action),
//     userName: userNameChange(state.userName, action)
//   }
// }

// 写法2:推荐
export default combineReducers({
  chatLog,
  statusMessage,
  userName: userNameChange
})

当需要使用拆分后的reducer时,只需要在srcindex.js

import rootReducer from './reducer'
const store = createStore(rootReducer)

中间件

位于“中间”,为“两侧”提供服务的函数。

redux-logger

npm i redux-logger安装包
index.js中修改下面几处:

import { applyMiddleware, createStore } from 'redux'
import { createLogger } from 'redux-logger'

const logger = createLogger()

const store = createStore(
  reducer,
  applyMiddleware(logger)
)

再次使用时,可以看到


redux-thunk

用来搭建异步action构造器

npm i redux-thunk安装包

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { applyMiddleware, createStore } from 'redux'
import { createLogger } from 'redux-logger'
import thunk from 'redux-thunk'
import reducer from './reducer'

const logger = createLogger()

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

function increment () {
  return {type: 'INCREMENT'}
}

function asyncIncrement () {
  return function (dispatch, getState) {
    setTimeout(() => {
      dispatch(increment())
    }, 1000);
  }
}

store.subscribe(() => {
  render()
})

const Counter = (props) => {
  return <div>
    <h1>{props.value}</h1>
    <button onClick={props.increment}>Increment</button>
    <button onClick={props.asyncIncrement}>AsyncIncrement</button>
  </div>
}

function render() {
  ReactDOM.render(
  <Counter 
    value={store.getState().increment}
    increment={() => store.dispatch(increment())}
    asyncIncrement={() => store.dispatch(asyncIncrement())}></Counter
  >, 
  document.getElementById('root'));
}

render()

react和redux的连接

  • 基本概念
    react-redux中组件分两类:ui组件和容器组件
    ui组件:只负责ui呈现,没逻辑
    容器组件:管理数据和业务逻辑,有内部状态,使用redux的api
    connect:用于从ui组件生成容器组件。

  • 小案例

目标:实现ui组件和容器组件的分离,实现点击+1和点击-1的功能

create-react-app react-redux-demo 创建新项目
npm i redux react-redux 安装包

文件结构


CouterContainer.js容器组件

import {connect} from 'react-redux'
import Counter from '../components/Counter'

const mapStateToProps = state => {
  return {
    value: state
  }
}

const mapDispatchToProps = dispatch => {
  return {
    handleIncrement () {
      dispatch({
        type: 'INCREMENT'
      })
    },
    handleDecrement () {
      dispatch({
        type: 'DECREMENT'
      })
    }
  }
}

const CounterContainer = connect (
  mapStateToProps,
  mapDispatchToProps
) (Counter)

export default CounterContainer

Counter.jsui组件

import React from 'react'

const Counter = (props) => {
  return (
    <div>
      <h1>Counter Component</h1>
      <h1>{props.value}</h1>
      <button onClick={props.handleIncrement}>点击+1</button>
      <button onClick={props.handleDecrement}>点击-1</button>
    </div>
  )
}

export default Counter

store > index.js

import {createStore} from 'redux'

function reducer (state = 100, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

const store = createStore(reducer)

export default store

index.js引入Providerstore

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux'
import store from './store'

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

app.js 引入容器组件

import React from 'react';
import CounterContainer from './containers/CouterContainer'

function App() {
  return (
    <div className="App">
      <CounterContainer />
    </div>
  );
}

export default App;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351