json-server和axios

安装json-server

  1. npm install json-server --save-dev

在package.json中添加script

  1. "scripts": {
  2. "server": "json-server -p3001 --watch db.json",
  3. // ...
  4. }

在项目根目录创建db.json文件

  1. {
  2. "notes": [
  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. }

使用命令**npm run server**启动json-server

安装axios

  1. npm install axios

在services/notes.js中编写get方法

  1. import axios from 'axios'
  2. const baseUrl = 'http://localhost:3001/notes'
  3. const getAll = async () => {
  4. const response = await axios.get(baseUrl)
  5. return response.data
  6. }
  7. export default { getAll }

初始化store

在noteReducer中添加INIT_NOTES action处理方法及action creator

  1. // ...
  2. const noteReducer = (state = [], action) => {
  3. console.log('ACTION:', action)
  4. switch (action.type) {
  5. case 'NEW_NOTE':
  6. return [...state, action.data]
  7. case 'INIT_NOTES':
  8. return action.data
  9. // ...
  10. }
  11. }
  12. export const initializeNotes = (notes) => {
  13. return {
  14. type: 'INIT_NOTES',
  15. data: notes,
  16. }
  17. }
  18. // ...

在index.js中初始化store

  1. import noteReducer, { initializeNotes } from './reducers/noteReducer'
  2. import noteService from './services/notes'
  3. // ...
  4. const store = createStore(reducer, composeWithDevTools())
  5. noteService.getAll().then(notes =>
  6. store.dispatch(initializeNotes(notes))
  7. )

将便签初始化移动到App组件中

  1. import React, {useEffect} from 'react'
  2. import NewNote from './components/NewNote'
  3. import Notes from './components/Notes'
  4. import VisibilityFilter from './components/VisibilityFilter'
  5. import noteService from './services/notes'
  6. import { initializeNotes } from './reducers/noteReducer'
  7. import { useDispatch } from 'react-redux'
  8. const App = () => {
  9. const dispatch = useDispatch()
  10. useEffect(() => {
  11. noteService
  12. .getAll().then(notes => dispatch(initializeNotes(notes)))
  13. }, [])
  14. return (
  15. <div>
  16. <NewNote />
  17. <VisibilityFilter />
  18. <Notes />
  19. </div>
  20. )
  21. }
  22. export default App

在useEffect中ESLint会有警告
image.png
解决方法一:将dispatch添加到[]

  1. const App = () => {
  2. const dispatch = useDispatch()
  3. useEffect(() => {
  4. noteService
  5. .getAll().then(notes => dispatch(initializeNotes(notes)))
  6. }, [dispatch])
  7. // ...
  8. }

当dispatch变量的值发生变化时,useEffect将再次执行(虽然这不是我们想要的)
解决方法二:添加注释禁用ESLint(不推荐)

  1. const App = () => {
  2. const dispatch = useDispatch()
  3. useEffect(() => {
  4. noteService
  5. .getAll().then(notes => dispatch(initializeNotes(notes)))
  6. },[]) // eslint-disable-line react-hooks/exhaustive-deps
  7. // ...
  8. }

创建新便签

在services/notes.js新建createNew方法

  1. const baseUrl = 'http://localhost:3001/notes'
  2. const getAll = async () => {
  3. const response = await axios.get(baseUrl)
  4. return response.data
  5. }
  6. const createNew = async (content) => {
  7. const object = { content, important: false }
  8. const response = await axios.post(baseUrl, object)
  9. return response.data
  10. }
  11. export default {
  12. getAll,
  13. createNew,
  14. }
  15. }

修改组件NewNote的addNote方法

  1. import React from 'react'
  2. import { useDispatch } from 'react-redux'
  3. import { createNote } from '../reducers/noteReducer'
  4. import noteService from '../services/notes'
  5. const NewNote = (props) => {
  6. const dispatch = useDispatch()
  7. const addNote = async (event) => {
  8. event.preventDefault()
  9. const content = event.target.note.value
  10. event.target.note.value = ''
  11. const newNote = await noteService.createNew(content)
  12. dispatch(createNote(newNote))
  13. }
  14. return (
  15. <form onSubmit={addNote}>
  16. <input name="note" />
  17. <button type="submit">add</button>
  18. </form>
  19. )
  20. }
  21. export default NewNote

修改action创建器createNote

  1. export const createNote = (data) => {
  2. return {
  3. type: 'NEW_NOTE',
  4. data,
  5. }
  6. }

Asynchronous actions and redux thunk

异步actions和redux thunk
与服务器的通信发生在组件的功能内部并不是很好。 如果能够将通信从组件中抽象出来就更好了。

redux thunk

redux thunk官方文档

Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters.

Redux Thunk中间件允许action creator返回一个函数,用于推迟action的dispatch或者在特定条件下才dispatch,内部函数接受store的dispatch和getState作为参数
安装redux-thunk

  1. npm install redux-thunk

在初始化store时允许使用thunk

  1. import { createStore, applyMiddleware } from 'redux';
  2. import thunk from 'redux-thunk';
  3. import rootReducer from './reducers/index';
  4. // Note: this API requires redux@>=3.1.0
  5. const store = createStore(rootReducer, applyMiddleware(thunk));

初始化时可以使用withExtraArgument传入更多参数,如果有多个额外参数,可传入对象,如{api, Whatever}

  1. const store = createStore(
  2. reducer,
  3. applyMiddleware(thunk.withExtraArgument(api)),
  4. );
  5. // later
  6. function fetchUser(id) {
  7. return (dispatch, getState, api) => {
  8. // you can use api here
  9. };
  10. }

使用thunk将通信移出组件

将store独立成单独的文件src/store.js

  1. import { createStore, combineReducers, applyMiddleware } from 'redux'
  2. import thunk from 'redux-thunk'
  3. import { composeWithDevTools } from 'redux-devtools-extension'
  4. import noteReducer from './reducers/noteReducer'
  5. import filterReducer from './reducers/filterReducer'
  6. const reducer = combineReducers({
  7. notes: noteReducer,
  8. filter: filterReducer,
  9. })
  10. const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk)))
  11. export default store

修改reducer里的action创建器createNote和initializeNotes使用thunk, 先从服务器获取数据,再dispatch

  1. import noteService from '../services/notes'
  2. const noteReducer = (state = [], action) => {
  3. // ...
  4. }
  5. export const createNote = (content) => {
  6. return async (dispatch) => {
  7. const newNote = await noteService.createNew(content)
  8. dispatch({
  9. type: 'NEW_NOTE',
  10. data: newNote,
  11. })
  12. }
  13. }
  14. // ...
  15. export const initializeNotes = () => {
  16. return async (dispatch) => {
  17. const notes = await noteService.getAll()
  18. dispatch({
  19. type: 'INIT_NOTES',
  20. data: notes,
  21. })
  22. }
  23. }
  24. export default noteReducer

原先组件内需要通信的地方,现在只需要简单地调用action creator

  1. const App = () => {
  2. const dispatch = useDispatch()
  3. useEffect(() => {
  4. dispatch(initializeNotes()))
  5. },[dispatch])
  6. // ...
  7. }
  1. const NewNote = () => {
  2. const dispatch = useDispatch()
  3. const addNote = async (event) => {
  4. event.preventDefault()
  5. const content = event.target.note.value
  6. event.target.note.value = ''
  7. dispatch(createNote(content))
  8. }
  9. // ...
  10. }

Exercise 6.15 - 6.18

实现投票功能

在servcie中添加update和get方法
注意,在json-server中id前不需要加冒号

  1. const update = async (id, content) => {
  2. const response = await axios.put(`${baseUrl}/${id}`, content)
  3. return response.data
  4. }
  5. const get = async (id) => {
  6. const response = await axios.get(`${baseUrl}/${id}`)
  7. return response.data
  8. }

修改vote action creator

  1. export const voteAction = (id) => {
  2. return async (dispatch) => {
  3. const anecdote = await anecdotesService.get(id)
  4. anecdote.votes += 1
  5. const votedAnecdote = await anecdotesService.update(id, anecdote)
  6. dispatch({
  7. type: 'VOTE',
  8. votedAnecdote,
  9. })
  10. }
  11. }

修改action对应的处理方法

  1. const anecdoteReducer = (state = [], action) => {
  2. switch (action.type) {
  3. case 'VOTE': {
  4. return state.map((item) =>
  5. item.id !== action.votedAnecdote.id ? item : action.votedAnecdote
  6. )
  7. }
  8. // ...
  9. }
  10. }