根据React推荐的状态管理约定,我们将状态和处理它的方法放在根组件中,通过props传递给其他组件,当应用程序变大时,状态管理将极具挑战。
Flux-architecture
Facebook 开发了 Flux 架构,使状态管理更加容易。
在 Flux 中,状态完全从 React-components 分离到自己的存储中。 存储中的状态不会直接更改,而是使用不同的 actions进行更改。当一个操作改变了存储的状态时,视图会被重新渲染
Redux
Facebook有一个Flux实现,但是我们使用Redux,它与Flux原理相同,但更简单,Facebook也在使用Redux
s
安装redux
npm install redux
使用redux实现计数器功能
import React from 'react'import ReactDOM from 'react-dom'import { createStore } from 'redux'const counterReducer = (state = 0, action) => {switch (action.type) {case 'INCREMENT':return state + 1case 'DECREMENT':return state - 1case 'ZERO':return 0default:return state}}const store = createStore(counterReducer)const App = () => {return (<div className="App">{store.getState()}<div><button onClick={() => store.dispatch({ type: 'INCREMENT' })}>plus</button><button onClick={() => store.dispatch({ type: 'DECREMENT' })}>minus</button><button onClick={() => store.dispatch({ type: 'ZERO' })}>zero</button></div></div>)}const renderApp = () => {ReactDOM.render(<App />, document.getElementById('root'))}renderApp()store.subscribe(renderApp)
- 状态存储在store中
存储的状态通过actions更改,Action是对象,至少有一个字段确定操作类型如
{type: 'INCREMENT'}
Action对状态的更改通过reducer来定义,reducer是一个函数,接受当前状态和action作为参数,返回新的状态。
// state改成xxx也是可以的const counterReducer = (state, action) => {if (action.type === 'INCREMENT') {return state + 1} else if (action.type === 'DECREMENT') {return state - 1} else if (action.type === 'ZERO') {return 0}return state}
reducer函数永远不要在其他地方使用,仅仅用来作为createStore的参数使用
const store = createStore(counterReducer)
action通过dispatch分发到store中来更改状态
store.dispatch({type: 'INCREMENT'})
使用getState()方法获取状态
const store = createStore(counterReducer)console.log(store.getState())store.dispatch({type: 'INCREMENT'})console.log(store.getState())
store拥有的第三个功能是subscribe,subscribe接受一个回调函数作为参数,当状态改变时执行这个函数
renderApp() // 第一次渲染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’ : ‘’} )}
<a name="oRS77"></a>### Pure functions, immutable**纯函数**是这样的:它们不会引起任何副作用,当使用相同的参数调用时,它们必须始终返回相同的结果。<br />Redux的reducer必须是纯函数<br />当使用push方法时,修改了原来的state数组, 这违反了纯函数原则,改用concat返回一个新数组```javascriptconst noteReducer = (state = [], action) => {if (action.type === 'NEW_NOTE') {return state.concat(action.data)}return state}
将reducer移动到src/reducers/noteReducer.js 中
添加deep-freeze库 ,它可以用来确保 reducer 被正确定义为不可变函数。
编写测试文件src/reducers/noteReducer.test.js, 使用命令CI=true npm test运行测试
import noteReducer from './noteReducer'import deepFreeze from 'deep-freeze'describe('noteReducer', () => {test('returns new state with action NEW_NOTE', () => {const state = []const action = {type: 'NEW_NOTE',data: {content: 'the app state is in redux store',important: true,id: 1}}deepFreeze(state)const newState = noteReducer(state, action)expect(newState).toHaveLength(1)expect(newState).toContainEqual(action.data)})})
deepFreeze让stae变成不可变的,如果使用push方法,测试将不能通过
测试TOGGLE_IMPORTANCE
test('returns new state with action TOGGLE_IMPORTANCE', () => {const state = [{content: 'the app state is in redux store',important: true,id: 1,},{content: 'state changes are made with actions',important: false,id: 2,},]const action = {type: 'TOGGLE_IMPORTANCE',data: {id: 2,},}deepFreeze(state)const newState = noteReducer(state, action)expect(newState).toHaveLength(2)expect(newState).toContainEqual(state[0])expect(newState).toContainEqual({content: 'state changes are made with actions',important: true,id: 2,})})
reducer中添加对应action处理方式
const noteReducer = (state = [], action) => {switch (action.type) {case 'NEW_NOTE':return state.concat(action.data)case 'TOGGLE_IMPORTANCE': {const id = action.data.idconst noteToChange = state.find((n) => n.id === id)const changedNote = {...noteToChange,important: !noteToChange.important,}return state.map((note) => (note.id !== id ? note : changedNote))}default:return state}}export default noteReducer
Array spread syntax
数组展开语法
case 'NEW_NOTE':return state.concat(action.data)
可以改写为
case 'NEW_NOTE':return [...state, action.data]
通过解构方式从数组获取元素
const numbers = [1, 2, 3, 4, 5, 6]const [first, second, ...rest] = numbersconsole.log(first) // prints 1console.log(second) // prints 2console.log(rest) // prints [3, 4, 5, 6]
Uncontrolled form
如果input组件给了name属性值,可以通过event.target.name值.value获取元素内容
<form onSubmit={addNote}><input name="note" /><button type="submit">add</button></form>
addNote = (event) => {event.preventDefault()const content = event.target.note.valueevent.target.note.value = ''store.dispatch({type: 'NEW_NOTE',data: {content,important: false,id: generateId()}})}
Action creators
const addNote = (event) => {event.preventDefault()const content = event.target.note.valueevent.target.note.value = ''store.dispatch({type: 'NEW_NOTE',data: {content,important: false,id: generateId(),},})}const toggleImportance = (id) => {store.dispatch({type: 'TOGGLE_IMPORTANCE',data: { id },})}
Redux组件不需要知道action的内部表示,将action的创建行为分离到自己的功能中
创建action的函数称为action creators
const createNote = (content) => {return {type: 'NEW_NOTE',data: {content,important: false,id: generateId(),},}}const toggleImportanceOf = (id) => {return {type: 'TOGGLE_IMPORTANCE',data: { id },}}const App = () => {const addNote = (event) => {event.preventDefault()const content = event.target.note.valueevent.target.note.value = ''store.dispatch(createNote(content))}const toggleImportance = (id) => {store.dispatch(toggleImportanceOf(id))}// ...}
Forwarding Redux-Store to various components
如何让所有组件访问store?
目前最新也最简单的方法是使用react-redux的hooks-api
安装react-redux
npm install react-redux
将action creators移到reducer中
const noteReducer = (state = [], action) => {// ...}const generateId = () =>Number((Math.random() * 1000000).toFixed(0))export const createNote = (content) => {return {type: 'NEW_NOTE',data: {content,important: false,id: generateId()}}}export const toggleImportanceOf = (id) => {return {type: 'TOGGLE_IMPORTANCE',data: { id }}}export default noteReducer
一个module可以有多个正常导出和一个默认导出
正常导出导入时要用花括号
import { createNote, toggleImportanceOf } from './reducers/noteReducer'
将App组件代码移到它自己的文件中, 通过Provider给App提供store
import React from 'react'import ReactDOM from 'react-dom'import { createStore } from 'redux'import { Provider } from 'react-redux'import App from './App'import noteReducer from './reducers/noteReducer'const store = createStore(noteReducer)ReactDOM.render(<Provider store={store}><App /></Provider>,document.getElementById('root'))
组件通过useDispatch来使用dispatch函数
import { useSelector, useDispatch } from 'react-redux'const App = () => {const dispatch = useDispatch()// ...const toggleImportance = (id) => {dispatch(toggleImportanceOf(id))}// ...}
使用useSelector访问store中的数据
import { useSelector, useDispatch } from 'react-redux'const App = () => {// ...const notes = useSelector(state => state)// ...}
useSelector接受一个函数作为参数,以获取想要的数据
const importantNotes = useSelector(state => state.filter(note => note.important))
exercise 6.3 - 6.8
实现排序功能
const anecdotes = useSelector((state) =>state.sort((a, b) => b.votes - a.votes))
