根据React推荐的状态管理约定,我们将状态和处理它的方法放在根组件中,通过props传递给其他组件,当应用程序变大时,状态管理将极具挑战。

Flux-architecture

Facebook 开发了 Flux 架构,使状态管理更加容易。
在 Flux 中,状态完全从 React-components 分离到自己的存储中。 存储中的状态不会直接更改,而是使用不同的 actions进行更改。当一个操作改变了存储的状态时,视图会被重新渲染
image.png

Redux

Facebook有一个Flux实现,但是我们使用Redux,它与Flux原理相同,但更简单,Facebook也在使用Redux
s
安装redux

  1. npm install redux

使用redux实现计数器功能

  1. import React from 'react'
  2. import ReactDOM from 'react-dom'
  3. import { createStore } from 'redux'
  4. const counterReducer = (state = 0, action) => {
  5. switch (action.type) {
  6. case 'INCREMENT':
  7. return state + 1
  8. case 'DECREMENT':
  9. return state - 1
  10. case 'ZERO':
  11. return 0
  12. default:
  13. return state
  14. }
  15. }
  16. const store = createStore(counterReducer)
  17. const App = () => {
  18. return (
  19. <div className="App">
  20. {store.getState()}
  21. <div>
  22. <button onClick={() => store.dispatch({ type: 'INCREMENT' })}>
  23. plus
  24. </button>
  25. <button onClick={() => store.dispatch({ type: 'DECREMENT' })}>
  26. minus
  27. </button>
  28. <button onClick={() => store.dispatch({ type: 'ZERO' })}>zero</button>
  29. </div>
  30. </div>
  31. )
  32. }
  33. const renderApp = () => {
  34. ReactDOM.render(<App />, document.getElementById('root'))
  35. }
  36. renderApp()
  37. store.subscribe(renderApp)
  • 状态存储在store中
  • 存储的状态通过actions更改,Action是对象,至少有一个字段确定操作类型如

    1. {
    2. type: 'INCREMENT'
    3. }
  • Action对状态的更改通过reducer来定义,reducer是一个函数,接受当前状态和action作为参数,返回新的状态。

    1. // state改成xxx也是可以的
    2. const counterReducer = (state, action) => {
    3. if (action.type === 'INCREMENT') {
    4. return state + 1
    5. } else if (action.type === 'DECREMENT') {
    6. return state - 1
    7. } else if (action.type === 'ZERO') {
    8. return 0
    9. }
    10. return state
    11. }
  • reducer函数永远不要在其他地方使用,仅仅用来作为createStore的参数使用

    1. const store = createStore(counterReducer)
  • action通过dispatch分发到store中来更改状态

    1. store.dispatch({type: 'INCREMENT'})
  • 使用getState()方法获取状态

    1. const store = createStore(counterReducer)
    2. console.log(store.getState())
    3. store.dispatch({type: 'INCREMENT'})
    4. console.log(store.getState())
  • store拥有的第三个功能是subscribe,subscribe接受一个回调函数作为参数,当状态改变时执行这个函数

    1. renderApp() // 第一次渲染
    2. store.subscribe(renderApp) // 状态改变时重新渲染

    Redux-notes

    dispatch传递的参数就是action ```javascript const noteReducer = (state = [], action) => { if (action.type === ‘NEW_NOTE’) { state.push(action.data) return state }

    return state }

const store = createStore(noteReducer)

store.dispatch({ type: ‘NEW_NOTE’, data: { content: ‘the app state is in redux store’, important: true, id: 1 } })

store.dispatch({ type: ‘NEW_NOTE’, data: { content: ‘state changes are made with actions’, important: false, id: 2 } })

const App = () => { return(

    {store.getState().map(note=>
  • {note.content} {note.important ? ‘important’ : ‘’}
  • )}
) }

  1. <a name="oRS77"></a>
  2. ### Pure functions, immutable
  3. **纯函数**是这样的:它们不会引起任何副作用,当使用相同的参数调用时,它们必须始终返回相同的结果。<br />Redux的reducer必须是纯函数<br />当使用push方法时,修改了原来的state数组, 这违反了纯函数原则,改用concat返回一个新数组
  4. ```javascript
  5. const noteReducer = (state = [], action) => {
  6. if (action.type === 'NEW_NOTE') {
  7. return state.concat(action.data)
  8. }
  9. return state
  10. }

将reducer移动到src/reducers/noteReducer.js 中
添加deep-freeze库 ,它可以用来确保 reducer 被正确定义为不可变函数。
编写测试文件src/reducers/noteReducer.test.js, 使用命令CI=true npm test运行测试

  1. import noteReducer from './noteReducer'
  2. import deepFreeze from 'deep-freeze'
  3. describe('noteReducer', () => {
  4. test('returns new state with action NEW_NOTE', () => {
  5. const state = []
  6. const action = {
  7. type: 'NEW_NOTE',
  8. data: {
  9. content: 'the app state is in redux store',
  10. important: true,
  11. id: 1
  12. }
  13. }
  14. deepFreeze(state)
  15. const newState = noteReducer(state, action)
  16. expect(newState).toHaveLength(1)
  17. expect(newState).toContainEqual(action.data)
  18. })
  19. })

deepFreeze让stae变成不可变的,如果使用push方法,测试将不能通过

测试TOGGLE_IMPORTANCE

  1. test('returns new state with action TOGGLE_IMPORTANCE', () => {
  2. const state = [
  3. {
  4. content: 'the app state is in redux store',
  5. important: true,
  6. id: 1,
  7. },
  8. {
  9. content: 'state changes are made with actions',
  10. important: false,
  11. id: 2,
  12. },
  13. ]
  14. const action = {
  15. type: 'TOGGLE_IMPORTANCE',
  16. data: {
  17. id: 2,
  18. },
  19. }
  20. deepFreeze(state)
  21. const newState = noteReducer(state, action)
  22. expect(newState).toHaveLength(2)
  23. expect(newState).toContainEqual(state[0])
  24. expect(newState).toContainEqual({
  25. content: 'state changes are made with actions',
  26. important: true,
  27. id: 2,
  28. })
  29. })

reducer中添加对应action处理方式

  1. const noteReducer = (state = [], action) => {
  2. switch (action.type) {
  3. case 'NEW_NOTE':
  4. return state.concat(action.data)
  5. case 'TOGGLE_IMPORTANCE': {
  6. const id = action.data.id
  7. const noteToChange = state.find((n) => n.id === id)
  8. const changedNote = {
  9. ...noteToChange,
  10. important: !noteToChange.important,
  11. }
  12. return state.map((note) => (note.id !== id ? note : changedNote))
  13. }
  14. default:
  15. return state
  16. }
  17. }
  18. export default noteReducer

Array spread syntax

数组展开语法

  1. case 'NEW_NOTE':
  2. return state.concat(action.data)

可以改写为

  1. case 'NEW_NOTE':
  2. return [...state, action.data]

通过解构方式从数组获取元素

  1. const numbers = [1, 2, 3, 4, 5, 6]
  2. const [first, second, ...rest] = numbers
  3. console.log(first) // prints 1
  4. console.log(second) // prints 2
  5. console.log(rest) // prints [3, 4, 5, 6]

Uncontrolled form

如果input组件给了name属性值,可以通过event.target.name值.value获取元素内容

  1. <form onSubmit={addNote}>
  2. <input name="note" />
  3. <button type="submit">add</button>
  4. </form>
  1. addNote = (event) => {
  2. event.preventDefault()
  3. const content = event.target.note.value
  4. event.target.note.value = ''
  5. store.dispatch({
  6. type: 'NEW_NOTE',
  7. data: {
  8. content,
  9. important: false,
  10. id: generateId()
  11. }
  12. })
  13. }

Action creators

  1. const addNote = (event) => {
  2. event.preventDefault()
  3. const content = event.target.note.value
  4. event.target.note.value = ''
  5. store.dispatch({
  6. type: 'NEW_NOTE',
  7. data: {
  8. content,
  9. important: false,
  10. id: generateId(),
  11. },
  12. })
  13. }
  14. const toggleImportance = (id) => {
  15. store.dispatch({
  16. type: 'TOGGLE_IMPORTANCE',
  17. data: { id },
  18. })
  19. }

Redux组件不需要知道action的内部表示,将action的创建行为分离到自己的功能中
创建action的函数称为action creators

  1. const createNote = (content) => {
  2. return {
  3. type: 'NEW_NOTE',
  4. data: {
  5. content,
  6. important: false,
  7. id: generateId(),
  8. },
  9. }
  10. }
  11. const toggleImportanceOf = (id) => {
  12. return {
  13. type: 'TOGGLE_IMPORTANCE',
  14. data: { id },
  15. }
  16. }
  17. const App = () => {
  18. const addNote = (event) => {
  19. event.preventDefault()
  20. const content = event.target.note.value
  21. event.target.note.value = ''
  22. store.dispatch(createNote(content))
  23. }
  24. const toggleImportance = (id) => {
  25. store.dispatch(toggleImportanceOf(id))
  26. }
  27. // ...
  28. }

Forwarding Redux-Store to various components

如何让所有组件访问store?
目前最新也最简单的方法是使用react-reduxhooks-api
安装react-redux

  1. npm install react-redux

将action creators移到reducer中

  1. const noteReducer = (state = [], action) => {
  2. // ...
  3. }
  4. const generateId = () =>
  5. Number((Math.random() * 1000000).toFixed(0))
  6. export const createNote = (content) => {
  7. return {
  8. type: 'NEW_NOTE',
  9. data: {
  10. content,
  11. important: false,
  12. id: generateId()
  13. }
  14. }
  15. }
  16. export const toggleImportanceOf = (id) => {
  17. return {
  18. type: 'TOGGLE_IMPORTANCE',
  19. data: { id }
  20. }
  21. }
  22. export default noteReducer

一个module可以有多个正常导出和一个默认导出
正常导出导入时要用花括号

  1. import { createNote, toggleImportanceOf } from './reducers/noteReducer'

将App组件代码移到它自己的文件中, 通过Provider给App提供store

  1. import React from 'react'
  2. import ReactDOM from 'react-dom'
  3. import { createStore } from 'redux'
  4. import { Provider } from 'react-redux'
  5. import App from './App'
  6. import noteReducer from './reducers/noteReducer'
  7. const store = createStore(noteReducer)
  8. ReactDOM.render(
  9. <Provider store={store}>
  10. <App />
  11. </Provider>,
  12. document.getElementById('root')
  13. )

组件通过useDispatch来使用dispatch函数

  1. import { useSelector, useDispatch } from 'react-redux'
  2. const App = () => {
  3. const dispatch = useDispatch()
  4. // ...
  5. const toggleImportance = (id) => {
  6. dispatch(toggleImportanceOf(id))
  7. }
  8. // ...
  9. }

使用useSelector访问store中的数据

  1. import { useSelector, useDispatch } from 'react-redux'
  2. const App = () => {
  3. // ...
  4. const notes = useSelector(state => state)
  5. // ...
  6. }

useSelector接受一个函数作为参数,以获取想要的数据

  1. const importantNotes = useSelector(state => state.filter(note => note.important))

exercise 6.3 - 6.8

实现排序功能

  1. const anecdotes = useSelector((state) =>
  2. state.sort((a, b) => b.votes - a.votes)
  3. )