Redxu(RTK) 基础 数据流基础 第3.1节 创建一个Post part2 文章列表和添加新文章

这一节大概能对应文档的数据流结构

项目启动

大家好,经过rtk快速上手和前个视频的开发环境搭建,下面我们开始真正动手实践吖。

从文档的探索初始项目开始,但是稍微有区别,以视频为准吖。

首先按照文档建立项目结构,
然后安装必要的依赖库(注意,我用了v6 是v6版本的react-router吖)
npm install react-router react-router-dom -S

安装 react-router和react-router-dom

npm install @types/react-router @types/react-router-dom -D

安装对应的type 定义文件

emmm,在基本内容之外,其实我们主要建立/api和/app两个夹子(因为我们使用了文档,这两个夹子自动建立辣捏)

  • /src
    • /app
      • store.ts 放置数据仓库
      • hook.ts 放类型变换后的useDispatch 和useSelector
    • /features 放置功能切片 (注意把示范用的counter切片删除。。或者仅仅留着好参考)
    • index.ts 整个程序的入口文件
    • App.tsx 应用的入口文件
    • /api (虽然我没建立,但是按照文档示范,稍后会有这个东东)
      • client.ts 小的 AJAX 请求客户端,用来发起 GET 和 POST 请求。
      • server.ts 为我们的数据提供模拟的的 REST API。我们的应用程序将在稍后从这些模拟的接口获取数据。

如果您现在加载应用程序,您应该会看到标题和欢迎消息。我们还可以打开 Redux DevTools Extension,看到我们的初始 Redux 状态完全为空。

有了这个,让我们开始吧!

主页的文章列表

创建文章功能对应的slice
  1. 创建slice

  2. 在store中导入 reducer

/features/posts/postSlice
import { createSlice } from "@reduxjs/toolkit";

const initialState = [
  { id: "1", title: "First Post!", content: "Hello!" },
  { id: "2", title: "Second Post", content: "More text" },
];

const postsSlice = createSlice({
  name: "posts",
  initialState: initialState,

  reducers: {
    postAdd: (state, action) => {
      state.push(action.payload);
    },
  },
});


export const { postAdd } = postsSlice.actions;

export default postsSlice.reducer;
/app/store
import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit";
import counterReducer from "../features/counter/counterSlice";
import postsReducer from "../features/posts/postsSlice";
export const store = configureStore({
  reducer: {
    // counter: counterReducer,
    posts: postsReducer,
  },
});

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
  ReturnType,
  RootState,
  unknown,
  Action<string>
>;
展示文章列表

这里要建立一个PostList 组件,作用就是把store中存储的数据作为post 展示到页面上。

import React from 'react'
import { useAppSelector } from '../../app/hooks'

export const PostsList = () => {
  const posts = useAppSelector(state => state.posts)

  const renderedPosts = posts.map(post => (
    <article className="post-excerpt" key={post.id}>
      <h3>{post.title}</h3>
      <p className="post-content">{post.content.substring(0, 100)}</p>
    </article>
  ))

  return (
    <section className="posts-list">
      <h2>Posts</h2>
      {renderedPosts}
    </section>
  )
}

然后在App.tsx中放置一个路由,并引入PostList组件

import React from "react";
import {
  BrowserRouter as Router,
  Routes,
  Route,
  Navigate,
} from "react-router-dom";

import PostList from "./features/posts/PostList";

function App() {
  return (
    <Router>
      <div className="App">
        <Routes>
          <Route path="/" element={<PostList />} />
          <Route path="*" element={<div>
            This is nowhere
          </div>} />
        </Routes>
      </div>
    </Router>
  );
}

export default App;

现在,页面上应该出现这些东东辣。

添加新文章

先写一个增加post的 表单吖。

//features/posts/AddPostForm.tsx
import React, { useState } from "react";

export const AddPostForm = () => {
  const [title, setTitle] = useState("");
  const [content, setContent] = useState("");

  const onTitleChanged = (e: React.ChangeEvent<HTMLInputElement>) =>
    setTitle(e.target.value);
  const onContentChanged = (e: React.ChangeEvent<HTMLTextAreaElement>) =>
    setContent(e.target.value);

  return (
    <section>
      <h2>添加新文章</h2>
      <form>
        <label htmlFor="postTitle">文章标题:</label>
        <input
          type="text"
          id="postTitle"
          name="postTitle"
          value={title}
          onChange={onTitleChanged}
        />
        <label htmlFor="postContent">内容:</label>
        <textarea
          id="postContent"
          name="postContent"
          value={content}
          onChange={onContentChanged}
        />
        <button type="button">保存文章</button>
      </form>
    </section>
  );
};

然后更新下App.tsx,把 AddPostForm 放进页面捏!

现在页面上出现了新增文章的表单了噜

保存文章

好了,下面是从0到1的过程辣,要新增文章,真的好兴奋吖。

emmmm,实际上新增文章就是要用dispatch发起一个action,

action里边当然要放payload,然后store收到action,根据action,选取对应的reducer处理数据,然后改变状态,就是这个流程,喵。

还是要提一嘴,因为RTK,action不用手动编写了,我们只需要简单写处理数据的reducer就行了。

那么去到postSlice,新增一个addPost的 reducer捏。

slice 就大概下面酱紫:

import { createSlice } from "@reduxjs/toolkit";

const initialState = [
  { id: "1", title: "First Post!", content: "Hello!" },
  { id: "2", title: "Second Post", content: "More text" },
];

const postsSlice = createSlice({
  name: "posts",
  initialState,
  reducers: {
    postAdd:(state,action)=>{
      state.push(action.payload)
    }
  },
});

export default postsSlice.reducer;
export const {postAdd}=postsSlice.actions

Dispatch "添加文章" Action

slice中的reducer写好了,最后只要绑定ui,在用户操作的时候dispatch一个action就行了捏,具体操作如下吖:

注意RTK提供的nanoid吖,这就是简化版的uuid吖

import React, { useState } from "react";
import { nanoid } from "@reduxjs/toolkit";
import { postAdd } from "./postSlice";
import { useAppDispatch } from "../../app/hooks";
export const AddPostForm = () => {
  const [title, setTitle] = useState("");
  const [content, setContent] = useState("");

  const onTitleChanged = (e: React.ChangeEvent<HTMLInputElement>) =>
    setTitle(e.target.value);
  const onContentChanged = (e: React.ChangeEvent<HTMLTextAreaElement>) =>
    setContent(e.target.value);

  const dispatch = useAppDispatch();

  const onSavePostClicked = () => {
    if (title && content) {
      dispatch(
        postAdd({
          id: nanoid(),
          title,
          content,
        })
      );

      setTitle("");
      setContent("");
    }
  };
  return (
    <section>
      <h2>添加新文章</h2>
      <form>
        <label htmlFor="postTitle">文章标题:</label>
        <input
          type="text"
          id="postTitle"
          name="postTitle"
          value={title}
          onChange={onTitleChanged}
        />
        <label htmlFor="postContent">内容:</label>
        <textarea
          id="postContent"
          name="postContent"
          value={content}
          onChange={onContentChanged}
        />
        <button type="button" onClick={onSavePostClicked}>
          保存文章
        </button>
      </form>
    </section>
  );
};

现在,尝试输入标题和一些文本,然后单击“保存文章”。 您应该会在文章列表中看到该文章的新项目。

恭喜!你刚刚构建了你的第一个 React + Redux 应用程序!

这显示了完整的 Redux 数据流周期:

  • 使用 useSelector 从 store 读取初始文章列表并渲染 UI
  • 我们 dispatch 了包含新文章条目数据的 postAdded action
  • postsReducer 监听到了 postAdded 动作,并用新条目更新了 posts 数组
  • Redux store 告诉 UI 一些数据已经改变
  • 文章列表读取更新后的文章数组,并重新渲染 UI 以显示新文章

在此之后我们将添加的所有新功能都将遵循相同的这个模式:添加状态 slice、编写 reducer 函数、dispatch action 以及基于 Redux store 中的数据渲染 UI。

总结
  • Redux state 由 reducer 函数来更新:
    • Reducers 总是通过复制现有状态值,更新副本来不可变地生成新状态
    • Redux Toolkit createSlice 函数为您生成“slice reducer”函数,并让您编写 “mutable 可变”代码,内部自动将其转变为安全的不可变更新
    • 这些 slice 化 reducer 函数被添加到 configureStore 中的 reducer 字段中,并定义了 Redux store 中的数据和状态字段名称
  • React 组件使用 useSelector 钩子从 store 读取数据
    • 选择器函数接收整个 state 对象,并且返回需要的部分数据
    • 每当 Redux store 更新时,选择器将重新运行,如果它们返回的数据发生更改,则组件将重新渲染
  • React 组件使用 useDispatch 钩子 dispatch action 来更新 store
    • createSlice 将为我们添加到 slice 的每个 reducer 函数生成 action creator 函数
    • 在组件中调用 dispatch(someActionCreator()) 来 dispatch action
    • Reducers 将运行,检查此 action 是否相关,并在适当时返回新状态
    • 表单输入值等临时数据应保留为 React 组件状态。当用户完成表单时,dispatch 一个 Redux action 来更新 store。
posted @ 2023-02-21 20:35  刘老六  阅读(50)  评论(0编辑  收藏  举报