如何在 React,Redux 中部分获取数据?
How to fetch data partially in react, redux?
操作
import { FETCH_BLOG, FETCH_BLOG_ERROR, FETCH_BLOG_LOADING } from "../constants/blogActionTypes"
const initialState = {
blogs: [],
error: '',
loading: false,
allBlogs: []
}
// eslint-disable-next-line import/no-anonymous-default-export
export default (blogs = initialState, action) => {
switch (action.type) {
case FETCH_BLOG_LOADING:
return {
blogs: [...blogs.blogs],
loading: true,
error: ''
};
case FETCH_BLOG_ERROR:
return {
blogs: [...blogs.blogs],
loading: false,
error: action.payload
};
case FETCH_BLOG:
return {
blogs: [...action.payload, ...blogs.blogs],
loading: false,
error: ''
};
default: return blogs;
}
}
减速器
export const fetchBlogs = (data) => async (dispatch) =>{
dispatch({ type: FETCH_BLOG_LOADING, payload: true })
fetch('http://localhost:5000/blog?show=' + data, {
method: 'GET',
headers: {
authorization: userData.token
}
})
.then(res => res.json())
.then(data => {
if (data.message) {
dispatch(fetchBlogsError(data.message))
} else {
dispatch({ type: FETCH_BLOG, payload: data })
}
})
}
反应
const [fetchData, setFetchData] = useState(0);
const showData = () => {
setFetchData(fetchData + 10)
}
const dispatch = useDispatch();
const { loading, error, blogs, } = useSelector(state => state.blogs)
const getData = useCallback( () => {
dispatch(fetchBlogs(fetchData))
}, [fetchData])
useEffect(() => {
getData()
}, [getData])
在第一个渲染中,我获取了 10 个 items.after 单击加载更多我从数据库中获取了另外 10 个数据。在博客组件上没问题,但回到主页后又回到博客页面;博客项目重复。如何解决此重复问题>
这里有两个相互关联的问题,您可能不需要解决 #2,具体取决于您如何解决 #1。
- 您应该为您的 thunk 操作添加一个条件,这样您就不会获取您之前已获取的页面。
- 您应该按页面分隔您的博客项目,这样如果您两次获取第 1 页,就不会总是将最新的项目附加到数组的末尾。
旁注:[...blogs.blogs]
是不必要的,因为有理由克隆您不更改的属性。
我对你的 API 电话感到困惑。看起来 /blog?show=20
正在获取第 21-30 个帖子,但我认为根据名称 show
应该是第 1-20 个帖子。
使用位置索引:
import { createAsyncThunk, createReducer } from "@reduxjs/toolkit";
export const fetchBlogs = createAsyncThunk(
"blogs/fetchBlog",
async (startIndex, { getState, rejectWithValue }) => {
const res = await fetch("http://localhost:5000/blog?show=" + startIndex, {
method: "GET",
headers: {
// where does userData come from ??
authorization: userData.token
}
});
const data = await res.json();
if (data.message) {
rejectWithValue(data.message);
} else {
return data;
}
},
{
condition: (startIndex, { getState }) => {
const { blogs } = getState();
// cancel if loading of if first post on paage is loaded
if (blogs.loading || blogs.blogs[startIndex]) {
return false;
}
}
}
);
const initialState = {
blogs: [],
error: "",
loading: false
};
export default createReducer(initialState, (builder) =>
builder
.addCase(fetchBlogs.pending, (state) => {
state.loading = true;
state.error = "";
})
.addCase(fetchBlogs.rejected, (state, action) => {
state.loading = false;
state.error = action.payload ?? action.error;
})
.addCase(fetchBlogs.fulfilled, (state, action) => {
const startIndex = action.meta.arg;
const newBlogs = action.payload;
// insert in the array at the correct position
state.blogs.splice(startIndex, newBlogs.length, newBlogs);
})
);
使用分隔页:
import { createAsyncThunk, createReducer, createSelector } from "@reduxjs/toolkit";
export const fetchBlogs = createAsyncThunk(
"blogs/fetchBlog",
async (pageNumber, { getState, rejectWithValue }) => {
const startIndex = 10 * (pageNumber - 1);
const res = await fetch("http://localhost:5000/blog?show=" + startIndex, {
method: "GET",
headers: {
// where does userData come from ??
authorization: userData.token
}
});
const data = await res.json();
if (data.message) {
rejectWithValue(data.message);
} else {
return data;
}
},
{
condition: (pageNumber, { getState }) => {
const { blogs } = getState();
// cancel if loading of if there is a property for this page
if (blogs.loading || blogs.blogs[pageNumber]) {
return false;
}
}
}
);
const initialState = {
//arrays keyed by page number
blogs: {},
error: "",
loading: false
};
export default createReducer(initialState, (builder) =>
builder
.addCase(fetchBlogs.pending, (state) => {
state.loading = true;
state.error = "";
})
.addCase(fetchBlogs.rejected, (state, action) => {
state.loading = false;
state.error = action.payload ?? action.error;
})
.addCase(fetchBlogs.fulfilled, (state, action) => {
const pageNumber = action.meta.arg;
state.blogs[pageNumber] = action.payload;
})
);
// want to flatten the blogs array when selecting
// create a memoized selector
export const selectBlogs = createSelector(
state => state.blogs,
(blogsState) => ({
...blogsState,
blogs: Object.values(blogsState.blogs).flat(1)
})
)
含组件:
export default () => {
const [pageNumber, setPageNumber] = useState(1);
const showNext = () => {
setPageNumber((page) => page + 1);
};
const dispatch = useDispatch();
const { loading, error, blogs } = useSelector(selectBlogs);
useEffect(() => {
dispatch(fetchBlogs(pageNumber));
}, [dispatch, pageNumber]);
操作
import { FETCH_BLOG, FETCH_BLOG_ERROR, FETCH_BLOG_LOADING } from "../constants/blogActionTypes"
const initialState = {
blogs: [],
error: '',
loading: false,
allBlogs: []
}
// eslint-disable-next-line import/no-anonymous-default-export
export default (blogs = initialState, action) => {
switch (action.type) {
case FETCH_BLOG_LOADING:
return {
blogs: [...blogs.blogs],
loading: true,
error: ''
};
case FETCH_BLOG_ERROR:
return {
blogs: [...blogs.blogs],
loading: false,
error: action.payload
};
case FETCH_BLOG:
return {
blogs: [...action.payload, ...blogs.blogs],
loading: false,
error: ''
};
default: return blogs;
}
}
减速器
export const fetchBlogs = (data) => async (dispatch) =>{
dispatch({ type: FETCH_BLOG_LOADING, payload: true })
fetch('http://localhost:5000/blog?show=' + data, {
method: 'GET',
headers: {
authorization: userData.token
}
})
.then(res => res.json())
.then(data => {
if (data.message) {
dispatch(fetchBlogsError(data.message))
} else {
dispatch({ type: FETCH_BLOG, payload: data })
}
})
}
反应
const [fetchData, setFetchData] = useState(0);
const showData = () => {
setFetchData(fetchData + 10)
}
const dispatch = useDispatch();
const { loading, error, blogs, } = useSelector(state => state.blogs)
const getData = useCallback( () => {
dispatch(fetchBlogs(fetchData))
}, [fetchData])
useEffect(() => {
getData()
}, [getData])
在第一个渲染中,我获取了 10 个 items.after 单击加载更多我从数据库中获取了另外 10 个数据。在博客组件上没问题,但回到主页后又回到博客页面;博客项目重复。如何解决此重复问题>
这里有两个相互关联的问题,您可能不需要解决 #2,具体取决于您如何解决 #1。
- 您应该为您的 thunk 操作添加一个条件,这样您就不会获取您之前已获取的页面。
- 您应该按页面分隔您的博客项目,这样如果您两次获取第 1 页,就不会总是将最新的项目附加到数组的末尾。
旁注:[...blogs.blogs]
是不必要的,因为有理由克隆您不更改的属性。
我对你的 API 电话感到困惑。看起来 /blog?show=20
正在获取第 21-30 个帖子,但我认为根据名称 show
应该是第 1-20 个帖子。
使用位置索引:
import { createAsyncThunk, createReducer } from "@reduxjs/toolkit";
export const fetchBlogs = createAsyncThunk(
"blogs/fetchBlog",
async (startIndex, { getState, rejectWithValue }) => {
const res = await fetch("http://localhost:5000/blog?show=" + startIndex, {
method: "GET",
headers: {
// where does userData come from ??
authorization: userData.token
}
});
const data = await res.json();
if (data.message) {
rejectWithValue(data.message);
} else {
return data;
}
},
{
condition: (startIndex, { getState }) => {
const { blogs } = getState();
// cancel if loading of if first post on paage is loaded
if (blogs.loading || blogs.blogs[startIndex]) {
return false;
}
}
}
);
const initialState = {
blogs: [],
error: "",
loading: false
};
export default createReducer(initialState, (builder) =>
builder
.addCase(fetchBlogs.pending, (state) => {
state.loading = true;
state.error = "";
})
.addCase(fetchBlogs.rejected, (state, action) => {
state.loading = false;
state.error = action.payload ?? action.error;
})
.addCase(fetchBlogs.fulfilled, (state, action) => {
const startIndex = action.meta.arg;
const newBlogs = action.payload;
// insert in the array at the correct position
state.blogs.splice(startIndex, newBlogs.length, newBlogs);
})
);
使用分隔页:
import { createAsyncThunk, createReducer, createSelector } from "@reduxjs/toolkit";
export const fetchBlogs = createAsyncThunk(
"blogs/fetchBlog",
async (pageNumber, { getState, rejectWithValue }) => {
const startIndex = 10 * (pageNumber - 1);
const res = await fetch("http://localhost:5000/blog?show=" + startIndex, {
method: "GET",
headers: {
// where does userData come from ??
authorization: userData.token
}
});
const data = await res.json();
if (data.message) {
rejectWithValue(data.message);
} else {
return data;
}
},
{
condition: (pageNumber, { getState }) => {
const { blogs } = getState();
// cancel if loading of if there is a property for this page
if (blogs.loading || blogs.blogs[pageNumber]) {
return false;
}
}
}
);
const initialState = {
//arrays keyed by page number
blogs: {},
error: "",
loading: false
};
export default createReducer(initialState, (builder) =>
builder
.addCase(fetchBlogs.pending, (state) => {
state.loading = true;
state.error = "";
})
.addCase(fetchBlogs.rejected, (state, action) => {
state.loading = false;
state.error = action.payload ?? action.error;
})
.addCase(fetchBlogs.fulfilled, (state, action) => {
const pageNumber = action.meta.arg;
state.blogs[pageNumber] = action.payload;
})
);
// want to flatten the blogs array when selecting
// create a memoized selector
export const selectBlogs = createSelector(
state => state.blogs,
(blogsState) => ({
...blogsState,
blogs: Object.values(blogsState.blogs).flat(1)
})
)
含组件:
export default () => {
const [pageNumber, setPageNumber] = useState(1);
const showNext = () => {
setPageNumber((page) => page + 1);
};
const dispatch = useDispatch();
const { loading, error, blogs } = useSelector(selectBlogs);
useEffect(() => {
dispatch(fetchBlogs(pageNumber));
}, [dispatch, pageNumber]);