React Redux入门

1 篇文章 0 订阅

目录

入门

我们应该什么时候使用?

Redux库和工具

Redux Toolkit

Redux DevTools 扩展

demo练习准备工作:

 基础示例

Redux Toolkit示例

Redux术语和概念

 不可变性Immutability

术语

 Redux步骤分解

练习的demo 

数据流基础-同步操作方式

异步逻辑与数据请求示例


入门

Redux 是一个使用叫做“action”的事件来管理和更新应用状态的模式和工具库 它以集中式Store(centralized store)的方式对整个应用中使用的状态进行集中管理,其规则确保状态只能以可预测的方式更新。

我们应该什么时候使用?

  • 在应用的大量地方,都存在大量的状态
  • 应用状态会随着时间的推移而频繁更新
  • 更新该状态的逻辑可能很复杂
  • 中型和大型代码量的应用,很多人协同开发

Redux库和工具

Redux 是一个小型的独立 JS 库。 但是,它通常与其他几个包一起使用:

React-Redux

Redux可以与任何UI框架集成,最常用于React。React-Redux是我们的官方软件包,它允许您的React组件通过读取状态片段和调度操作来更新存储,从而与Redux存储交互。

Redux Toolkit

Redux Toolkit是我们推荐的编写 Redux 逻辑的方法。 它包含我们认为对于构建 Redux 应用程序必不可少的包和函数。 Redux Toolkit 构建在我们建议的最佳实践中,简化了大多数 Redux 任务,防止了常见错误,并使编写 Redux 应用程序变得更加容易。

Redux DevTools 扩展

Redux DevTools扩展程序可以显示 Redux 存储中状态随时间变化的历史记录。这允许您有效地调试应用程序,包括使用强大的技术,如“时间旅行调试”。

demo练习准备工作:

安装Redux Toolkit

# NPM
npm install @reduxjs/toolkit

# Yarn
yarn add @reduxjs/toolkit

创建一个React Redux应用,使用 官方 Redux+JS 模版 它基于Create React App,它利用了Redux Toolkit和Redux与React组件的集成。

npx create-react-app my-app --template redux

安装Redux

# NPM
npm install redux

# Yarn
yarn add redux

 配套工具

npm install react-redux
npm install --save-dev redux-devtools

 基础示例

应用的整体全局状态以对象树的方式存放于单个 store。 唯一改变状态树(state tree)的方法是创建 action,一个描述发生了什么的对象,并将其 dispatch 给 store。要指定状态树如何响应 action 来进行更新,你可以编写纯 reducer 函数,这些函数根据旧 state 和 action 来计算新 state。

import { createStore } from 'redux'

function counterReducer(state = { value: 0 }, action) {
  switch (action.type) {
    case 'counter/incremented':
      return { value: state.value + 1 }
    case 'counter/decremented':
      return { value: state.value - 1 }
    default:
      return state
  }
}

let store = createStore(counterReducer)

store.subscribe(() => console.log(store.getState()))

store.dispatch({ type: 'counter/incremented' })
// {value: 1}
store.dispatch({ type: 'counter/incremented' })
// {value: 2}
store.dispatch({ type: 'counter/decremented' })
// {value: 1}

Redux Toolkit示例

Redux Toolkit 简化了编写 Redux 逻辑和设置 store 的过程。 使用 Redux Toolkit,相同的逻辑如下所示:

import { createSlice, configureStore } from '@reduxjs/toolkit'

const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0
  },
  reducers: {
    incremented: state => {
      state.value += 1
    }
  }
})

export const { incremented, decremented } = counterSlice.actions

const store = configureStore({
  reducer: counterSlice.reducer
})

// Can still subscribe to the store
store.subscribe(() => console.log(store.getState()))

store.dispatch(incremented())
// {value: 1}

Redux术语和概念

  • state:驱动应用的真实数据源头
  • view:基于当前状态的 UI 声明性描述
  • actions:根据用户输入在应用程序中发生的事件,并触发状态更新

 redux是单向数据流

 不可变性Immutability

"Mutable" 意为 "可改变的",而 "immutable 意为永不可改变。

JavaScript 的对象(object)和数组(array)默认都是 mutable 的。如果我创建一个对象,我可以更改其字段的内容。如果我创建一个数组,我也可以更改内容:

const obj = { a: 1, b: 2 }
// 对外仍然还是那个对象,但它的内容已经变了
obj.b = 3

const arr = ['a', 'b']
// 同样的,数组的内容改变了
arr.push('c')
arr[1] = 'd'

这就是 改变 对象或数组的例子。内存中还是原来对象或数组的引用,但里面的内容变化了。

如果想要不可变的方式来更新,代码必需先复制原来的 object/array,然后更新它的复制体

可以使用如下或其它方式

const obj1 = { name: '冰墩墩', info: {age: 18} };
// 方式1通过扩展运算符
const obj2 = {
        ...obj1, // obj1的备份
        info: {
            ...obj1.info,  // ojb1.info的备份
            age: 16, // 覆盖age
        }

    }
// 方式2通过Object.assign
const obj3 = Object.assign({}, obj1, {name: '冰墩墩', info: {age: 22}});

术语

Action是一个具有 type 字段的普通 JavaScript 对象。你可以将 action 视为描述应用程序中发生了什么的事件.

type 字段是一个字符串,给这个 action 一个描述性的名字,比如"todos/todoAdded"。我们通常把那个类型的字符串写成“域/事件名称”,其中第一部分是这个 action 所属的特征或类别,第二部分是发生的具体事情。

action 对象可以有其他字段,其中包含有关发生的事情的附加信息。按照惯例,我们将该信息放在名为 payload 的字段中。

一个典型的 action 对象可能如下所示:

const addTodoAction = {
  type: 'todos/todoAdded',
  payload: 'Buy milk'
}

Reducer

reducer 是一个函数,接收当前的 state 和一个 action 对象,必要时决定如何更新状态,并返回新状态。函数签名是:(state, action) => newState

Reducer 必需符合以下规则:

  • 仅使用 state 和 action 参数计算新的状态值
  • 禁止直接修改 state。必须通过复制现有的 state 并对复制的值进行更改的方式来做 不可变更新(immutable updates)
  • 禁止任何异步逻辑、依赖随机值或导致其他“副作用”的代码

const initialState = { value: 0 }

function counterReducer(state = initialState, action) {
  // 检查 reducer 是否关心这个 action
  if (action.type === 'counter/increment') {
    // 如果是,复制 `state`
    return {
      ...state,
      // 使用新值更新 state 副本
      value: state.value + 1
    }
  }
  // 返回原来的 state 不变
  return state
}

 store

当前 Redux 应用的状态存在于一个名为 store 的对象中。

store 是通过传入一个 reducer 来创建的,并且有一个名为 getState 的方法,它返回当前状态值:实际使用我们会通过useSelector来获取store

import { configureStore } from '@reduxjs/toolkit'

const store = configureStore({ reducer: counterReducer })

console.log(store.getState())

Dispatch

Redux store 有一个方法叫 dispatch更新 state 的唯一方法是调用 store.dispatch() 并传入一个 action 对象。 store 将执行所有 reducer 函数并计算出更新后的 state

store.dispatch({ type: 'counter/increment' })

console.log(store.getState())
// {value: 1}

dispatch 一个 action 可以形象的理解为 "触发一个事件"。发生了一些事情,我们希望 store 知道这件事。 Reducer 就像事件监听器一样,当它们收到关注的 action 后,它就会更新 state 作为响应。

const increment = () => {
  return {
    type: 'counter/increment'
  }
}

store.dispatch(increment())

console.log(store.getState())
// {value: 2}

 Redux步骤分解

  • 初始启动:
    • 使用最顶层的 root reducer 函数创建 Redux store
    • store 调用一次 root reducer,并将返回值保存为它的初始 state
    • 当 UI 首次渲染时,UI 组件访问 Redux store 的当前 state,并使用该数据来决定要呈现的内容。同时监听 store 的更新,以便他们可以知道 state 是否已更改。
  • 更新环节:
    • 应用程序中发生了某些事情,例如用户单击按钮
    • dispatch 一个 action 到 Redux store,例如 dispatch({type: 'counter/increment'})
    • store 用之前的 state 和当前的 action 再次运行 reducer 函数,并将返回值保存为新的 state
    • store 通知所有订阅过的 UI,通知它们 store 发生更新
    • 每个订阅过 store 数据的 UI 组件都会检查它们需要的 state 部分是否被更新。
    • 发现数据被更新的每个组件都强制使用新数据重新渲染,紧接着更新网页

练习的demo 

数据流基础-同步操作方式

根据上面的学习,练习了以下简单demo,里面写的详细注释有点过于啰嗦,仅用于自己后面复盘.

demo相关有:展示贴子列表,查看贴子详情,添加贴子,编辑贴子,显示作者,下面只列举了关键几个文件,有的子组件没有贴过来,均是简单展示组件。

项目的index.js文件

涉及以下三个知识点

1.ReactDOM.render(template,targetDOM),该方法接收两个参数:第一个是创建的模板,多个dom元素外层需使用一个标签进行包裹,如<div>;第二个参数是插入该模板的目标位置。我们总是必须调用 ReactDOM.render(<App />) 来告诉 React 开始渲染我们的根 <App> 组件。

2.使用Provider, 为了让像 useSelector 这样的 hooks 正常工作,我们需要使用一个名为 <Provider> 的组件在幕后传递 Redux store,以便他们可以访问它。

我们在这里引用来自 app/store.js 中创建的 store。然后,用 <Provider> 包裹整个 <App>,并传入 store:<Provider store={store}>,现在,任何调用 useSelector 或 useDispatch 的 React 组件都可以访问 <Provider> 中的 store。

3.React Router v6路由配置,详情查看

 代码实现

import React from 'react';
import ReactDOM from 'react-dom';
import { store } from './app/store';
import { Provider } from 'react-redux';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import App from './App';
import { PostsList  } from './features/posts/PostsList';
import { SinglePostPage } from './features/posts/SinglePostPage';
import { EditPostForm } from './features/posts/EditPostForm';
import * as serviceWorker from './serviceWorker';
import './index.css';

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <BrowserRouter>
        <Routes>
          <Route path='/' element={<App />} />
          {/* 嵌套路由 */}
          <Route path="/postList" element={<PostsList />}>
            <Route index element={<div>我是index默认子路由</div>} />
            <Route path="detail" element={<div>我是子路由元素1</div>} />
            <Route path=":id" element={<div>我是子路由元素2</div>} />
          </Route>
          <Route path="/postsDetail/:postId" element={<SinglePostPage />} />
          <Route path="/postsEdit/:postId" element={<EditPostForm />} />
          <Route path="*" element={<App />} />
        </Routes>
      </BrowserRouter>
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);
serviceWorker.unregister();

app/store.js文件

创建 Redux store 实例,导入 postsReducer等 函数,并更新对 configureStore 的调用,以便将 postsReducer 作为名为 posts 的 reducer 字段传递。

这告诉 Redux 我们希望我们的根部 state 对象内部有一个名为 posts 的字段,并且 state.posts 的所有数据都将在 dispatch action 时由 postsReducer 函数更新。

代码实现

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
import postsSlice from '../features/posts/postsSlice';
import usersSlice from '../features/users/usersSlice';

export const store = configureStore({
  reducer: {
    counter: counterReducer,
    posts: postsSlice,
    users: usersSlice,
  },
});

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

postsSlice.js文件

里面涉及的知识点全部注释在相关代码段处

代码实现

import { createSlice } from '@reduxjs/toolkit';
import { nanoid } from '@reduxjs/toolkit';
const initialState = [
  { id: 1, title: 'First Post', content: 'Hello', user: '1', reactions: { thumbsUp: 1, hooray:1, heart:1,rocket:1,eyes:0} },
  { id: 2, title: 'Second Post', content: 'More text', reactions: { thumbsUp: 1, hooray:1, heart:1,rocket:1,eyes:0} },
]
// 添加状态切片
// Redux Toolkit createSlice 函数生成“slice reducer”函数
const postsSlice = createSlice({
  name: 'posts',
  initialState,
  reducers: { // 编写 reducer 函数
    // postAdded(state, action) {
    //   // 请记住:reducer 函数必须总是通过复制来不可变地创建新的状态值! 
    //   // 这里调用诸如 Array.push() 之类的变异函数或修改 createSlice() 
    //   // 中的诸如 state.someField = someValue 之类的对象字段之所以是安全的,
    //   // 是因为它在内部使用 Immer 库将这些突变转换为安全的不可变更新,
    //   // 但不要尝试在 createSlice 之外用这种方式更改任何数据!!!
    //   state.push(action.payload)
    // },
    postAdded: {
      reducer(state, action) {
        state.push(action.payload);
      },
      prepare(title, content, userId) {
        // 前提: 唯一 ID 和其他随机值应该放在 action 中,而不是在 reducer 中计算.
        //  如果我们这里需要一个随机 id,但reducer里是不允许出现随机值的,那么 可以利用prepare函数
        //  createSlice 允许我们在编写 reducer 时定义一个 prepare 函数。 prepare 函数可以接受多个参数,生成诸如唯一 ID 之类的随机值
        // ,并运行需要的任何其他同步逻辑来决定哪些值进入 action 对象。然后它应该返回一个包含 payload 字段的对象。
        // (返回对象还可能包含一个 meta 字段,可用于向 action 添加额外的描述性值,以及一个 error 字段,
        // 该字段应该是一个布尔值,指示此 action 是否表示某种错误。)
        return {
          payload: {
            id: nanoid(),
            title,
            content,
            user: userId
          },
          meta: "meteeeeeeexxxx"
        }
      }
    },
    // 上面 postAdded 这种写法是因为要有随机值,分为两步完成 。如果不存在随机值或者 异步,
    // 那直接通过以下方式
    postUpadted(state, action) {
      const { id, title, content } = action.payload;
      console.log(JSON.stringify(state));
      const existingPost = state.find(post => post.id == id)
      if (existingPost) {
        existingPost.title = title;
        existingPost.content = content;
      }
    },
    reactionAdded(state, action) {
      const { postId, reaction } = action.payload
      const existingPost = state.find(post => post.id === postId)
      if (existingPost) {
        existingPost.reactions[reaction]++
      }
    }
  }
})
export const { postAdded, postUpadted, reactionAdded } = postsSlice.actions;
// 编写 postAdded reducer 函数时,createSlice 会自动生成一个同名的 “action creator” 函数
// 我们可以导出该动作创建者并在我们的 UI 组件中使用它来在用户单击“保存帖子”时分派动作。
// 默认情况下,createSlice 生成的 action creator 希望你传入一个参数,该值将作为 action.payload 放入 action 对象中
console.log('postsSlice', postsSlice)
export default postsSlice.reducer;

// 这些切片化 reducer 函数被添加到 configureStore 中的 reducer 字段中,并定义了 Redux store 中的数据和状态字段名称,见store.js

PostsList.js 贴子列表

import React from 'react';
import { useSelector } from 'react-redux';
import { PostAuthor } from './PostAuthor';
import { ReactionButtons } from './ReactionButtons';
import { Link, Outlet, useLocation, useNavigate, useParams, useSearchParams } from 'react-router-dom'

export const PostsList = () => {
  const posts = useSelector(state => state.posts);
  // const match = useMatch('postList/sent');
  // console.log('match', match);

  const location = useLocation();
  console.log('location', location);
  const navigate = useNavigate();
  console.log('navigate', navigate);


  const params = useParams();
  console.log('params', params);

  const [searchParams, setSearchparams] = useSearchParams();
  console.log('searchParams', searchParams);
  const renderedPosts = posts.map(post => (
    <div key={post.id}>
      <h3>{post.title}</h3>
      <p>{post.content.substring(0,100)}</p>
      <PostAuthor userId={post.user} /> 
      <Link to={`/postsDetail/${post.id}`}> 查看详情</Link>
      <Link to={`/postsEdit/${post.id}`}> 编辑贴子</Link>
      <ReactionButtons post={post} />
    </div>
  ))
  return (
    <section>
      <h2>posts</h2>
      {renderedPosts}
      {/* 子路由渲染位置 */}
      <Outlet />
      <div onClick={() => navigate('/postList', {replace: true})}>跳转</div>
    </section>
  )
}

AddPostForm.js添加贴子文件

import React, { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux';
import { nanoid } from '@reduxjs/toolkit';
import { postAdded } from './postsSlice';

export const AddPostForm = () => {
  const [title, setTitle] = useState('')
  const [content, setContent] = useState('')
  const [userId, setUserId] = useState('');
  const dispatch = useDispatch();
  const users = useSelector(state => state.users);

  const onTitleChanged = e => setTitle(e.target.value)
  const onContentChanged = e => setContent(e.target.value)
  const onAuthorChanged = e => setUserId(e.target.value);

  const canSave = Boolean(title) && Boolean(content) && Boolean(userId);

  const usersOptions = users.map(user => (
    <option key={user.id} value={user.id}>
      {user.name}
    </option>
  ))
  const handleSave = () => {
    if (title && content) {
      // dispatch(postAdded(
      //   {
      //     id: nanoid(),
      //     title,
      //     content,
      //   }
      // ))
      // 使用prepare调用 方式
      dispatch(postAdded( title,  content, userId))
      setTitle('');
      setContent('')
    }
  }
  return (
    <section>
      <h2>添加新帖子</h2>
      <form>
        <label htmlFor="postTitle">帖子标题:</label>
        <input
          type="text"
          id="postTitle"
          name="postTitle"
          value={title}
          onChange={onTitleChanged}
        />
        <label htmlFor='postAuthor'>用户:</label>
        <select id="postAuthor" value={userId} onChange={onAuthorChanged}>
          <option value=""></option>
          {usersOptions}
        </select>
        <label htmlFor="postContent">内容:</label>
        <textarea
          id="postContent"
          name="postContent"
          value={content}
          onChange={onContentChanged}
        />
        <button type="button" onClick={handleSave} disabled={!canSave}>保存帖子</button>
      </form>
    </section>
  )
}

异步逻辑与数据请求示例

使用 Redux Toolkit createAsyncThunk API 来简化异步调用

postsSlice.js关于createAsyncThunk片段

// 使用createAsyncThunk API生成thunk,当调用 dispatch(fetchPosts()) 的时候,
// fetchPosts 这个 thunk 会首先 dispatch 一个 action 类型为'posts/fetchPosts/pending':,
// 可以在reducer中通过extraReducers里[fetchPosts.pending]来监听这个状态 ,将状态更改为loading,
// 一旦Promise接口请求成功,extraReducers里的[fetchPosts.fulfilled]会接收到新的posts数组,即可更新数据
// 参数1:将用作生成的 action 类型的前缀的字符串
// 参数2:一个“payload creator”回调函数,它应该返回一个包含一些数据的 Promise,或者一个被拒绝的带有错误的 Promise
export const fetchPosts = createAsyncThunk('users/fetchPosts', async () => {
  const response = await client.get('http://127.0.0.1:8888/api/posts')
  console.log('responseresponse', response.data.users)
  return response.data.posts
})

export const addNewPost = createAsyncThunk('posts/addNewPost', async initialPost => {
  const response = await client.post('http://127.0.0.1:8888/api/posts', { post: initialPost});
  return response.post
})

添加以上thunk后,可以在createSlice的extraReducers里响应没有定义的切片(也就是异步请求类型,不同状态下产生的action)

const postsSlice = createSlice({
  name: 'posts',
  initialState,
  reducers: {
    //...忽略不写
  },
// !!有时切片的 reducer 需要响应 没有 定义到该切片的 reducers 字段中的 action。这个时候就需要使用 slice 中的 extraReducers 字段。
  // 例如异步请求类型,要想捕获他们的pending或fulfilled等action类型
  extraReducers: {
    // 'counter/increment': (state, action) => {
    //   // 更新帖子内容切片的 reducer 逻辑
    // }
    /**上面改为下面这种:
     * 更好的方法是,调用 actionCreator.toString()
     * actionCreator.toString()它会返回 Redux Toolkit 生成的 action 类型的字符串。??如下使用会得到右面的结果
     * 打印{ [increment]: () => {}} ==>{'counter/increment': () => {})}
     */
    [increment]: (state, action) => {
      //  更新帖子内容切片的 reducer 逻辑
    },
    /**
     * 需要监听由 fetchPosts thunk dispatch 的“pending”和“fulfilled” 2个 action 类型。
     * 这些 action 创建者被包含在了 fetchPosts 函数中,我们可以将它们传递给 extraReducers 来监听这些 action:
     * createAsyncThunk 返回的所有 3 种 action,都可以通过返回的 Promise 来处理。
     * 当请求开始时,我们将 status 枚举设置为 'loading'
     *  如果请求成功,我们将 status 标记为 'succeeded',并将获取的帖子添加到 state.posts
     * 如果请求失败,我们会将 status 标记为 'failed',并将错误消息保存到 state 中,需要的时候可以显示出来
     */
    [fetchPosts.pending]: (state, action) => {
      state.status = 'loading';
    },
    [fetchPosts.fulfilled]: (state, action) => {
      state.status = 'succeeded';
      state.posts = state.posts.concat(action.payload);
    },
    [fetchPosts.rejected]: (state, action) => {
      state.state = 'failed';
      state.error = action.error.message;
    },
    [addNewPost.fulfilled]: (state, action) => {
      state.posts.push(action.payload);
    }
  }
  // 如果我们为 extraReducers 传递一个函数而不是一个对象,我们可以使用 builder 参数来添加每个 case。builder.addCase() 函数接受一个纯字符串的 action 类型,或者一个 Redux Toolkit 的 action 创建者:
  // extraReducers: builder => {
  //   builder.addCase('counter/decrement', (state, action) => {})
  //   builder.addCase(increment,  (state, action) => {})
  // }

 PostsList.js里来dispatch刚刚添加 的fetchPosts,来获取接口数据

  useEffect(() => {
    if (postStatus === 'idle') {
      // 这里一定要是函数调用,一开始写的dispatch(fetchPosts),执行也没有报错,就是不发出请求,以为是哪里写的有问题,最后定位是这里没有调用
      dispatch(fetchPosts());
    }
  }, [postStatus, dispatch])

 上面的接口请求是本地node起的http服务,代码贴到下面

api.js

var express = require('express'); //express框架模块
var path = require('path'); //系统路径模块
var fs = require('fs'); //文件模块
var bodyParser = require('body-parser'); //对post请求的请求体进行解析模块
const { application } = require('express');
var app = express();
app.use(bodyParser.urlencoded({ extended: false })); //bodyParser.urlencoded 用来解析request中body的 urlencoded字符,只支持utf-8的编码的字符,也支持自动的解析gzip和 zlib。返回的对象是一个键值对,当extended为false的时候,键值对中的值就为'String'或'Array'形式,为true的时候,则可为任何数据类型。

// 设置允许跨域请求
app.all('*', function(req, res, next) {
    res.header('Access-Control-Allow-Origin', '*'); //访问控制允许来源:所有
    res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); //访问控制允许报头 X-Requested-With: xhr请求
    res.header('Access-Control-Allow-Metheds', 'PUT, POST, GET, DELETE, OPTIONS'); //访问控制允许方法
    res.header('X-Powered-By', 'nodejs'); //自定义头信息,表示服务端用nodejs
    res.header('Content-Type', 'application/json;charset=utf-8');
    next();
});

//创建get接口:获取用户列表
app.get('/api/users', function(req, res) {
	
	//console.log(req.body); //获取请求参数
	
    var file = path.join(__dirname, 'users.json'); //文件路径,__dirname为当前运行js文件的目录
	
	//读取json文件
    fs.readFile(file, 'utf-8', function(err, data) {
        if (err) {
            res.send('文件读取失败');
        } else {
            res.send(data);
        }
    });
});

//创建get接口:获取贴子列表
app.get('/api/posts', function(req, res) {
	//console.log(req.body); //获取请求参数
    var file = path.join(__dirname, 'posts.json'); //文件路径,__dirname为当前运行js文件的目录
	//读取json文件
    fs.readFile(file, 'utf-8', function(err, data) {
        if (err) {
            res.send('文件读取失败');
        } else {
            res.send(data);
        }
    });
});
app.post('/api/posts', function(req, res) {
    var postData = ""; 
    // 数据块接收中
    req.addListener("data", function (postDataChunk) {
        postData += postDataChunk;
    });
    // 数据接收完毕,执行回调函数
    req.addListener("end", function () {
        console.log('ok********', postData);
        fs.readFile('posts.json', 'utf-8', function(err, data) {
            const oldData = JSON.parse(data);
            oldData.posts.push({
                ...JSON.parse(postData).post,
                "reactions": { "thumbsUp": 0, "hooray": 0, "heart": 0, "rocket": 0,  "eyes": 0 }
            });
            // 先从文件中读出数据,然后push新添加的再写入
            fs.writeFile('posts.json',JSON.stringify(oldData), () => {} )
            res.send({
                data: null,
                status: 'success'
            })
        });
       
    });
})
var hostName = '127.0.0.1'; //ip
var port = 8888; //端口
app.listen(port, hostName, function() {
    console.log(`服务器运行在http://${hostName}:${port}`);
});

posts.json

{
  "code": 0,
  "msg": "请求成功",
  "posts": [
      {
          "id": "1",
          "title": "First Post",
          "content": "Hello",
          "user": "1",
          "reactions": {
              "thumbsUp": 0,
              "hooray": 0,
              "heart": 0,
              "rocket": 0,
              "eyes": 0
          }
      },
      {
          "id": "2",
          "title": "Second Post",
          "content": "'More text",
          "user": "2",
          "reactions": {
              "thumbsUp": 0,
              "hooray": 0,
              "heart": 0,
              "rocket": 0,
              "eyes": 0
          }
      },
      {
          "title": "我是通过接口添加的新数据",
          "content": "yes",
          "user": "迈克儿",
          "id": "ZEB8szswCzrgQ8RzpJeOK",
          "reactions": {
              "thumbsUp": 0,
              "hooray": 0,
              "heart": 0,
              "rocket": 0,
              "eyes": 0
          }
      },
      {
          "title": "今天 星期五",
          "content": "放学别走",
          "user": "1",
          "id": "Pjxh4hI12jN6pmB_u2_1Q",
          "reactions": {
              "thumbsUp": 0,
              "hooray": 0,
              "heart": 0,
              "rocket": 0,
              "eyes": 0
          }
      },
      {
          "title": "明天去公园",
          "content": "玉渊潭赏樱花",
          "user": "哪吒",
          "id": "InRwTt-jqNcYxIi1xrikW",
          "reactions": {
              "thumbsUp": 0,
              "hooray": 0,
              "heart": 0,
              "rocket": 0,
              "eyes": 0
          }
      }
  ]
}

 以上内容完成了redux通过redux toolkit实现同步和异步操作store。

此文根据【Redux中文官网】文档记录笔记

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值