Redux 基本用法

React 只是 DOM 的一个抽象层,不涉及代码结构组件间的通信。所以无法用 React 写大型应用,为了解决这个问题,引出了 Redux 架构。

有可能不需要 Redux

不需要使用 Redux 的情况:

  • 用户的使用方式非常简单
  • 用户之间没有协作
  • 不需要与服务器大量交互,也没有使用 WebSocket
  • 视图层(View)只从单一来源获取数据

Redux 的适用场景:多交互、多数据源

  • 用户的使用方式复杂
  • 不同身份的用户有不同的使用方式(比如普通用户和管理员)
  • 多个用户之间可以协作
  • 与服务器大量交互,或者使用了WebSocket
  • View 要从多个来源获取数据

从组件角度,使用 Redux 的场景:

  • 某个组件的状态,需要共享
  • 某个状态需要在任何地方都可以拿到
  • 一个组件需要改变全局状态
  • 一个组件需要改变另一个组件的状态
Redux 设计思想

1、Web 应用是一个状态机,视图与状态一一对应。
2、所有状态都保存在一个对象里面。

基本概念与 API

1、Store

保存数据的容器,整个应用只能有一个 Store。Redux 中使用 createStore 函数来生成 Store。

import { createStore } from 'redux'
// createStore 函数接受另一个函数作为参数,返回新生成的 Store 对象
const store = createStore(fn)

2、State

Store 对象包含所有数据,State 是 Store 中某个时点的数据。当前的 State 可以通过 store.getState() 得到。

import { createStore } from 'redux'
const store = createStore(fn)

const state = store.getState()

Redux 规定,一个 State 对应一个 View。

3、Action

State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。
Action 是一个对象。其中的type属性是必须的,表示 Action 的名称。其他属性可以自由设置。

const action = {
  type: 'ADD_TODO',
  payload: 'Learn Redux'
}

Action 描述当前发生的事情,使用 Action 运送数据到 Store,可以改变 State

4、Action Creator

用来生成 Action 的函数。因为 View 要发送多少种消息就需要多少种 Action,手写会很麻烦。

const ADD_TODO = '添加 TODO'
// addTodo函数就是一个 Action Creator
function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

const action = addTodo('Learn Redux')

5、store.dispatch()

store.dispatch() 是 View 发出 Action 的唯一方法。

import { createStore } from 'redux'
const store = createStore(fn)
// store.dispatch 接受一个 Action 对象作为参数,将它发送出去
store.dispatch({
  type: 'ADD_TODO',
  payload: 'Learn Redux'
})

结合 Action Creator

store.dispatch(addTodo('Learn Redux'))

6、Reducer

Store 收到 Action 以后,生成新的 State 的过程就叫 Reducer。Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。

const reducer = function (state, action) {
  // ...
  return new_state
}

可以将整个应用的初始状态,作为 State 的默认值。

const defaultState = 0;
const reducer = (state = defaultState, action) => {
  switch (action.type) {
    case 'ADD':
      return state + action.payload
    default: 
      return state
  }
};

const state = reducer(1, {
  type: 'ADD',
  payload: 2
})

在生成 Store 的时候,可以将 Reducer 传入 createStore 方法中,触发 Reducer 的自动执行。就可以不用手动调用。

import { createStore } from 'redux'
const store = createStore(reducer)

Reducer 函数可以作为参数传入 reduce方法中。

// 将 Action 对象按照顺序作为一个数组
const actions = [
  { type: 'ADD', payload: 0 },
  { type: 'ADD', payload: 1 },
  { type: 'ADD', payload: 2 }
];

const total = actions.reduce(reducer, 0); // 3

7、纯函数

Reducer 函数是一个纯函数,只要是同样的输入,一定得到同样的输出。这样可以保证同样的 State ,必定得到同样的 View。

Reducer函数作为纯函数必须遵守的约束:

  • 不得改写参数
  • 不能调用系统 I/O 的API
  • 不能调用 Date.now() 或者 Math.random() 等不纯的方法,因为每次会得到不一样的结果

Reducer 函数里面不能改变 State,必须返回一个全新的对象。

// State 是一个对象
function reducer(state, action) {
  return Object.assign({}, state, { thingToChange })
  // 或者
  return { ...state, ...newState }
}

// State 是一个数组
function reducer(state, action) {
  return [...state, newItem]
}

8、store.subscribe()

Store 允许使用 store.subscribe 方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。

import { createStore } from 'redux'
const store = createStore(reducer)

store.subscribe(listener)

只要把 View 的更新函数(对于 React 项目,就是组件的 render 方法或 setState 方法)放入 listen,就会实现 View 的自动渲染。

想要解除这个监听,只需要调用 store.subscribe 方法返回的一个函数。

let unsubscribe = store.subscribe(() =>
  console.log(store.getState())
)

unsubscribe()
Store 的实现

Store 提供了三个方法:

  • store.getState() 得到 State 的方法
  • store.dispatch() View 发出 Action 的方法
  • store.subscribe() State 的监听函数

createStore 方法还可以接受第二个参数,表示 State 的最初状态。这通常是服务器给出的。

// window.STATE_FROM_SERVER就是整个应用的状态初始值 
// 它会覆盖 Reducer 函数的默认初始值
let store = createStore(todoApp, window.STATE_FROM_SERVER)

createStore 方法的简单实现

const createStore = (reducer) => {
  let state
  let listeners = []

  const getState = () => state

  const dispatch = (action) => {
    state = reducer(state, action)
    listeners.forEach(listener => listener())
  };

  const subscribe = (listener) => {
    listeners.push(listener)
    return () => {
      listeners = listeners.filter(l => l !== listener)
    }
  };

  dispatch({})

  return { getState, dispatch, subscribe }
}
Reducer 的拆分

Reducer 函数负责生成 State。由于整个应用只有一个 State 对象,包含所有数据,对于大型应用来说,这个 State 必然十分庞大,导致 Reducer 函数也十分庞大。

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

上面代码中,三种 Action 分别改变 State 的三个属性:

  • ADD_CHAT:chatLog属性
  • CHANGE_STATUS:statusMessage属性
  • CHANGE_USERNAME:userName属性

这三个属性之间没有联系,这提示我们可以把 Reducer 函数拆分。不同的函数负责处理不同属性,最终把它们合并成一个大的 Reducer 即可。一个 React 根组件由很多子组件构成,这样拆分子组件与子 Reducer 完全可以对应。

const chatReducer = (state = defaultState, action = {}) => {
  return {
    // Reducer 函数被拆成了三个小函数,每一个负责生成对应的属性
    chatLog: chatLog(state.chatLog, action),
    statusMessage: statusMessage(state.statusMessage, action),
    userName: userName(state.userName, action)
  }
}

Redux 提供了一个 combineReducers 方法,用于 Reducer 的拆分。只要定义各个子 Reducer 函数,然后用这个方法,将它们合成一个大的 Reducer。

import { combineReducers } from 'redux'
// State 的属性名必须与子 Reducer 同名
const chatReducer = combineReducers({
  chatLog,
  statusMessage,
  userName
})

export default todoApp

当 State 的属性名与子 Reducer 不同名,参考以下写法:

const reducer = combineReducers({
  a: doSomethingWithA,
  b: processB,
  c: c
})

// 等同于
function reducer(state = {}, action) {
  return {
    a: doSomethingWithA(state.a, action),
    b: processB(state.b, action),
    c: c(state.c, action)
  }
}

combineReducer 的简单实现:

// 该函数根据 State 的 key 去执行相应的子 Reducer
// 并将返回结果合并成一个大的 State 对象
const combineReducers = reducers => {
  return (state = {}, action) => {
    return Object.keys(reducers).reduce(
      (nextState, key) => {
        nextState[key] = reducers[key](state[key], action);
        return nextState
      },
      {} 
    )
  }
}

使用时将子 Reducer 放在一个文件里面,然后统一引入

import { combineReducers } from 'redux'
import * as reducers from './reducers'

const reducer = combineReducers(reducers)
工作流程

1、用户发出 Action

store.dispatch(action)

2、Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action。 Reducer 会返回新的 State 。

let nextState = todoApp(previousState, action)

3、State 一旦有变化,Store 就会调用监听函数。

// 设置监听函数
store.subscribe(listener)

listener 可以通过 store.getState() 得到当前状态。如果使用的是 React,这时可以触发重新渲染 View。

function listerner() {
  let newState = store.getState()
  component.setState(newState)   
}
实例:计数器
const Counter = ({ value, onIncrement, onDecrement }) => (
  <div>
  <h1>{value}</h1>
  <button onClick={onIncrement}>+</button>
  <button onClick={onDecrement}>-</button>
  </div>
)

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

const store = createStore(reducer);

const render = () => {
  ReactDOM.render(
    // 为Counter添加递增和递减的 Action
    <Counter
      // 每次 State 的变化都会导致网页重新渲染
      value={store.getState()}
      onIncrement={() => store.dispatch({type: 'INCREMENT'})}
      onDecrement={() => store.dispatch({type: 'DECREMENT'})}
    />,
    document.getElementById('root')
  )
}

render()
// Store 的监听函数设置为render
store.subscribe(render)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值