json-server和axios
安装json-server
npm install json-server --save-dev
在package.json中添加script
"scripts": {
"server": "json-server -p3001 --watch db.json",
// ...
}
在项目根目录创建db.json文件
{
"notes": [
{
"content": "the app state is in redux store",
"important": true,
"id": 1
},
{
"content": "state changes are made with actions",
"important": false,
"id": 2
}
]
}
使用命令**npm run server**
启动json-server
安装axios
npm install axios
在services/notes.js中编写get方法
import axios from 'axios'
const baseUrl = 'http://localhost:3001/notes'
const getAll = async () => {
const response = await axios.get(baseUrl)
return response.data
}
export default { getAll }
初始化store
在noteReducer中添加INIT_NOTES action处理方法及action creator
// ...
const noteReducer = (state = [], action) => {
console.log('ACTION:', action)
switch (action.type) {
case 'NEW_NOTE':
return [...state, action.data]
case 'INIT_NOTES':
return action.data
// ...
}
}
export const initializeNotes = (notes) => {
return {
type: 'INIT_NOTES',
data: notes,
}
}
// ...
在index.js中初始化store
import noteReducer, { initializeNotes } from './reducers/noteReducer'
import noteService from './services/notes'
// ...
const store = createStore(reducer, composeWithDevTools())
noteService.getAll().then(notes =>
store.dispatch(initializeNotes(notes))
)
将便签初始化移动到App组件中
import React, {useEffect} from 'react'
import NewNote from './components/NewNote'
import Notes from './components/Notes'
import VisibilityFilter from './components/VisibilityFilter'
import noteService from './services/notes'
import { initializeNotes } from './reducers/noteReducer'
import { useDispatch } from 'react-redux'
const App = () => {
const dispatch = useDispatch()
useEffect(() => {
noteService
.getAll().then(notes => dispatch(initializeNotes(notes)))
}, [])
return (
<div>
<NewNote />
<VisibilityFilter />
<Notes />
</div>
)
}
export default App
在useEffect中ESLint会有警告
解决方法一:将dispatch添加到[]
const App = () => {
const dispatch = useDispatch()
useEffect(() => {
noteService
.getAll().then(notes => dispatch(initializeNotes(notes)))
}, [dispatch])
// ...
}
当dispatch变量的值发生变化时,useEffect将再次执行(虽然这不是我们想要的)
解决方法二:添加注释禁用ESLint(不推荐)
const App = () => {
const dispatch = useDispatch()
useEffect(() => {
noteService
.getAll().then(notes => dispatch(initializeNotes(notes)))
},[]) // eslint-disable-line react-hooks/exhaustive-deps
// ...
}
创建新便签
在services/notes.js新建createNew方法
const baseUrl = 'http://localhost:3001/notes'
const getAll = async () => {
const response = await axios.get(baseUrl)
return response.data
}
const createNew = async (content) => {
const object = { content, important: false }
const response = await axios.post(baseUrl, object)
return response.data
}
export default {
getAll,
createNew,
}
}
修改组件NewNote的addNote方法
import React from 'react'
import { useDispatch } from 'react-redux'
import { createNote } from '../reducers/noteReducer'
import noteService from '../services/notes'
const NewNote = (props) => {
const dispatch = useDispatch()
const addNote = async (event) => {
event.preventDefault()
const content = event.target.note.value
event.target.note.value = ''
const newNote = await noteService.createNew(content)
dispatch(createNote(newNote))
}
return (
<form onSubmit={addNote}>
<input name="note" />
<button type="submit">add</button>
</form>
)
}
export default NewNote
修改action创建器createNote
export const createNote = (data) => {
return {
type: 'NEW_NOTE',
data,
}
}
Asynchronous actions and redux thunk
异步actions和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
npm install redux-thunk
在初始化store时允许使用thunk
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
// Note: this API requires redux@>=3.1.0
const store = createStore(rootReducer, applyMiddleware(thunk));
初始化时可以使用withExtraArgument传入更多参数,如果有多个额外参数,可传入对象,如{api, Whatever}
const store = createStore(
reducer,
applyMiddleware(thunk.withExtraArgument(api)),
);
// later
function fetchUser(id) {
return (dispatch, getState, api) => {
// you can use api here
};
}
使用thunk将通信移出组件
将store独立成单独的文件src/store.js
import { createStore, combineReducers, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
import noteReducer from './reducers/noteReducer'
import filterReducer from './reducers/filterReducer'
const reducer = combineReducers({
notes: noteReducer,
filter: filterReducer,
})
const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk)))
export default store
修改reducer里的action创建器createNote和initializeNotes使用thunk, 先从服务器获取数据,再dispatch
import noteService from '../services/notes'
const noteReducer = (state = [], action) => {
// ...
}
export const createNote = (content) => {
return async (dispatch) => {
const newNote = await noteService.createNew(content)
dispatch({
type: 'NEW_NOTE',
data: newNote,
})
}
}
// ...
export const initializeNotes = () => {
return async (dispatch) => {
const notes = await noteService.getAll()
dispatch({
type: 'INIT_NOTES',
data: notes,
})
}
}
export default noteReducer
原先组件内需要通信的地方,现在只需要简单地调用action creator
const App = () => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(initializeNotes()))
},[dispatch])
// ...
}
const NewNote = () => {
const dispatch = useDispatch()
const addNote = async (event) => {
event.preventDefault()
const content = event.target.note.value
event.target.note.value = ''
dispatch(createNote(content))
}
// ...
}
Exercise 6.15 - 6.18
实现投票功能
在servcie中添加update和get方法
注意,在json-server中id前不需要加冒号
const update = async (id, content) => {
const response = await axios.put(`${baseUrl}/${id}`, content)
return response.data
}
const get = async (id) => {
const response = await axios.get(`${baseUrl}/${id}`)
return response.data
}
修改vote action creator
export const voteAction = (id) => {
return async (dispatch) => {
const anecdote = await anecdotesService.get(id)
anecdote.votes += 1
const votedAnecdote = await anecdotesService.update(id, anecdote)
dispatch({
type: 'VOTE',
votedAnecdote,
})
}
}
修改action对应的处理方法
const anecdoteReducer = (state = [], action) => {
switch (action.type) {
case 'VOTE': {
return state.map((item) =>
item.id !== action.votedAnecdote.id ? item : action.votedAnecdote
)
}
// ...
}
}