根据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 + 1
case 'DECREMENT':
return state - 1
case 'ZERO':
return 0
default:
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返回一个新数组
```javascript
const 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.id
const 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] = numbers
console.log(first) // prints 1
console.log(second) // prints 2
console.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.value
event.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.value
event.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.value
event.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)
)