前端 -- react快速入门

1、创建react项目

1、使用 React 的脚手架工具来搭建项目

javascript 复制代码
npx create-react-app my-app
cd my-app
npm start

2、JSX基础语法

1、基础语法

React 中,使用 JSX 来描述页面。

js 复制代码
function App() {
  return (
    <div>Hello React~</div>
  );
}

你可以把类似于 HTML 的代码单独提取出来,例如:

js 复制代码
function App() {
  const ele = <div>Hello React~</div>
  return (
    ele
  );
}

JSX的本质其实就是 React.createElement 方法的一种语法糖。

javascript 复制代码
const element2 = React.createElement(
    'h1',
    {className: 'greeting'},
    'Hello, world!'
);

注意这种类似于 HTML 的语法在 React 中称之为 JSX , 这是一种 JavaScript 的语法扩展。在 React 中推荐使用 JSX 来描述用户界面。JSX 乍看起来可能比较像是模版语言,但事实上它完全是在 JavaScript 内部实现的。

使用 JSX 来描述页面时,有如下的一些语法规则:

  • 根元素只能有一个
  • JSX 中使用 JavaScript 表达式。表达式写在花括号 {}
  • 属性值指定为字符串字面量,或者在属性值中插入一个 JavaScript 表达式
  • style 对应样式对象,class 要写作 className
  • 注释需要写在花括号
  • JSX 允许在模板中插入数组,数组会自动展开所有成员

2、组件与事件绑定

React 中无法通过 return false 来阻止默认行为,所以只有使用 e.preventDefault 的方式来阻止默认行为。

javascript 复制代码
function Form() {
  function handleSubmit(e) {
    e.preventDefault();
    console.log('You clicked submit.');
  }

  return (
    <form onSubmit={handleSubmit}>
      <button type="submit">Submit</button>
    </form>
  );
}

如果是类组件,那么事件处理函数写作一个类方法。// 现在不怎么用类组件了

javascript 复制代码
class Welcome extends React.Component {
  // 事件处理函数
  eventHandler(e){
    window.alert('Hello');
    e.preventDefault();
  }
  
  render() {
    return (
      <a href="https://www.baidu.com/" onClick={this.eventHandler}>this is a test</a>
    );
  }
}

3、组件状态

组件状态

早期类组件被称之为有状态组件,就是因为在类组件中能够维护组件数据。

js 复制代码
class 类名 extends React.Component{
  constructor(){
    super();
    // 设置组件自身的数据状态
    this.state = {
      xxx : xxx
    }
  }
  render(){
    return (
    	// 通过 {this.state.xxx} 来获取状态数据
    )
  }
}

// 或者
class 类名 extends React.Component{
  state = {
      xxx : xxx
  }
  render(){
    return (
    	// 通过 {this.state.xxx} 来获取状态数据
    )
  }
}

不要直接去修改状态值,而是应该通过 setState 方法修改组件的 state 状态数据。

js 复制代码
this.setState({
  xxx: 新值
})

setState ,它对状态的改变,可能是异步的。

如果改变状态的代码处于某个 HTML 元素的事件中,则其是异步的,否则是同步

如果在事件处理函数里面想拿到 setState 执行后的数据,可以提前使用一个变量来存储计算结果,或者使用 setState 的第二个参数,它是一个函数,这个函数会在 state 更新后被调用。
React 会对异步的 setState 进行优化,将多次 setState 进行合并(将多次状态改变完成后,再统一对 state 进行改变,然后触发 render

4、props传参

Vue 一样,在 React 中组件会存在层级关系,那么自然会涉及到组件之间进行数据的传递。

如果是父组件向子组件传递数据,则使用 props

如果是函数组件,props 作为函数的一个参数传入:

js 复制代码
function 组件名(props) {
  return (
    // 一段 JSX
    // 通过 props.xxx 获取传入的值
    <div>
      <p>姓名:{props.name}</p>
      <p>年龄:{props.age}</p>
      <p>性别:{props.gender}</p>   
    </div>
  );
}

如果是类组件,则需要在 constructor 中将 props 通过 super 传递给父类,然后通过 this.props 的方式来获取传入的值:

js 复制代码
class 组件名 extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
       // 一段 JSX
    	 // 通过 this.props.xxx 获取传入的值
        <div>
          <p>姓名:{this.props.name}</p>
          <p>年龄:{this.props.age}</p>
          <p>性别:{this.props.gender}</p>   
        </div>
     );
	}
}

通过 props.children ,可以实现类似于 Vue 中插槽的功能,例如:

js 复制代码
class 组件B extends React.Component{
  constructor(props){
    super(props);
  }
  render(){
    return (
      <div>
          {this.props.children}
      </div>
    );
  }
}
class 组件A extends React.Component{
  constructor(props){
    super(props);
  }
  render(){
    return (
      <组件B>
        <p>Hello, React</p>
        <p>Hello, Redux</p>
        <p>Hello, Facebook</p>
        <p>Hello, Google</p>
      </组件B>
    );
  }
}

通过 defaultprops 就可以对 props 设置默认值。

js 复制代码
function Greeting(props) {
  const { name, age, gender } = props;
  return (
    <div>
      <p>姓名:{name}</p>
      <p>年龄:{age}</p>
      <p>性别:{gender}</p>   
    </div>
   );
}
// 设置默认的 props 值,当组件没有传值时会使用默认值
Greeting.defaultProps = {
  name : 'xiejie',
  age : 18,
  gender : 'male'
};
js 复制代码
class Greeting extends React.Component {
  constructor(props) {
    super(props);
  }
  // 设置默认的 defaultProps 属性值
  static defaultProps = {
    name : "xiejie",
    age : 18,
    gender : 'male' 
  }
  render() {
    return (
      <div>
        <p>姓名:{this.props.name}</p>
        <p>姓名:{this.props.age}</p>
        <p>姓名:{this.props.gender}</p>
      </div>
    );
  }
}
// 或者
Greeting.defaultProps = {
    name : "xiejie",
    age : 18,
    gender : 'male' 
}

props 的类型检查

javascript 复制代码
import PropTypes from 'prop-types';

class Greeting extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}

Greeting.propTypes = {
  name: PropTypes.string
};
javascript 复制代码
import PropTypes from 'prop-types'

function HelloWorldComponent({ name }) {
  return (
    <div>Hello, {name}</div>
  )
}

HelloWorldComponent.propTypes = {
  name: PropTypes.string
}

export default HelloWorldComponent

5、状态提升

父传子通过 props ,子传父通过触发自定义事件。

React 中,如果子组件需要向父组件传递数据,同样是通过触发父组件传递给子组件的事件来进行传递,被称之为"状态提升"。

父组件

javascript 复制代码
import React from "react";
import Money from "./component/Money";
// 类组件
class App extends React.Component {
  state = {
    dollar: "",
    rmb: ""
  }
  transformToRMB = (value) => {}
  transformToDollar = (value) => {}
  
  render() {
    return (
      <div>
        <Money text="美元" money={this.state.dollar} transform={this.transformToRMB} />
        <Money text="人民币" money={this.state.rmb} transform={this.transformToDollar} />
      </div>
    )
  }
}

export default App;

子组件

js 复制代码
import React from 'react';
function Money(props) {
    function handleChange(e){
       // 在子组件中,要做的事情很简单,将用户数据的值,传递给父组件
       // 让父组件来进行修改
       props.transform(e.target.value);
    }
    return (
        <fieldset>
            <legend>{props.text}</legend>
            <input type="text" value={props.money} onChange={handleChange}/>
        </fieldset>
    );
}
export default Money;

6、受控和非受控组件

受控组件

受控组件,本质上其实就是将表单中的控件和视图模型(状态)进行绑定,之后都是针对状态进行操作。

javascript 复制代码
import React from "react";

// 类组件
class App extends React.Component {
  state = {
    value : ""
  }

  handleChange = (e) => {
    this.setState({
      value : e.target.value
    })
  }

  clickHandle = () => {
    // 提交整个表单
    console.log(`你要提交的内容为:${this.state.value}`);
  }

  render() {
    return (
      <div>
        <input type="text" value={this.state.value} onChange={this.handleChange}/>
        <button onClick={this.clickHandle}>提交</button>
      </div>
    )
  }

}

export default App;
非受控组件

在某些特殊情况下,需要使用以前传统的 DOM 方案进行处理,此时替代的方案就是非受控组件。

javascript 复制代码
import React from "react";

// 类组件
class App extends React.Component {
  constructor(){
    super();
    // 创建了一个 ref,回头我们就可以获取到和该 ref 绑定了的 DOM 节点
    this.inputCon = React.createRef();
  }
  clickHandle = () => {
    // 通过 this.inputCon.current 可以获取到 input DOM 节点
    console.log(this.inputCon.current.value);
  }
  render() {
    return (
      <div>
        <input type="text" ref={this.inputCon}/>
        <button onClick={this.clickHandle}>获取用户输入的内容</button>
      </div>
    )
  }
}
export default App;

非受控组件的默认值

html 复制代码
<div>
  {/* 一旦你用了 value,React 会认为你这是一个受控组件 */}
  {/* 如果想要给默认值,使用 defaultValue */}
  <input type="text" ref={this.inputCon} defaultValue="123"/>
  <button onClick={this.clickHandle}>获取用户输入的内容</button>
</div>

3、生命周期

所谓生命周期,指的是组件从诞生到销毁会经历一系列的过程,该过程就叫做生命周期。
生命周期钩子函数是属于类组件所独有的东西 ,但是从 React 16.8 推出 Hooks 以来,整体已经开始以函数组件为主,因此仅介绍一些常用的生命周期钩子函数。

常用的生命周期钩子函数如下:

  • constructor

    • 同一个组件对象只会创建一次
    • 不能在第一次挂载到页面之前,调用 setState ,为了避免问题,构造函数中严禁使用 setState
  • render

    • render 是整个类组件中必须要书写的生命周期方法
    • 返回一个虚拟 DOM ,会被挂载到虚拟 DOM 树中,最终渲染到页面的真实 DOM
    • render 可能不只运行一次,只要需要重新渲染,就会重新运行
    • 严禁使用 setState,因为可能会导致无限递归渲染
javascript 复制代码
import React from "react";

// 类组件
class App extends React.Component {

  constructor() {
    super();
    // 主要做一些初始化操作,例如该组件的状态
    this.state = {
      value : 1
    }
    console.log("constructor");
  }


  clickHandle=()=>{
    this.setState({
      value : this.state.value + 1
    })
  }

  render() {
    console.log("render");
    return (
      <div>
        <div>{this.state.value}</div>
        <button onClick={this.clickHandle}>+1</button>
      </div>
    )
  }

}

export default App;
  • componentDidMount
    • 类似于 Vue 里面的 mounted
    • 只会执行一次
    • 可以使用 setState
    • 通常情况下,会将网络请求、启动计时器等一开始需要的操作,书写到该函数中
  • componentWillUnmount
    • 通常在该函数中销毁一些组件依赖的资源,比如计时器

4、Hooks

HookReact 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
Hooks 的出现,首先能解决如下的一些问题:

  • 告别令人疑惑的生命周期
    • 例如下面的例子,相同的代码在不同的生命周期中存在了两份
js 复制代码
import React from "react";
// 类组件
class App extends React.Component {
  constructor() {
    super();
    this.state = {
      count : 0
    }
  }
  componentDidMount(){
    document.title = `你点击了${this.state.count}次`;
  }
  componentDidUpdate(){
    document.title = `你点击了${this.state.count}次`;
  }
  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    )
  }
}
export default App;
  • 告别类组件中烦人的 this
    • 在类组件中,会存在 this 的指向问题,例如在事件处理函数中,不能直接通过 this 获取组件实例,需要修改 this 指向
  • 告别繁重的类组件,回归前端程序员更加熟悉的函数

另外,Hooks 的出现,还有更加重要的一个信号,那就是整个 React 思想上面的转变,从"面向对象"的思想开始转为"函数式编程"的思想。这是编程范式上面的转变。

Hook 就是 JavaScript 函数,但是使用它们会有两个额外的规则:

  • 只能在函数最外层 调用 Hook。不要在循环、条件判断或者子函数中调用。
  • 只能在 React 的函数组件 中调用 Hook 。不要在其他 JavaScript 函数中调用。

1、useStateuseEffect

一个是为函数组件添加状态的 useState ,另一个是处理函数副作用的 useEffect

useState
javascript 复制代码
import { useState } from 'react';

function App(props) {
  let [age, setAge] = useState(18);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: '学习 Hook' }]);

  function clickhandle(){
    setAge(++age);
  }
  return (
    <div>
      <div>年龄:{age}</div>
      <div>水果:{fruit}</div>
      <div>待办事项:{todos[0].text}</div>
      <button onClick={clickhandle}>+1</button>
    </div>
  );
}
export default App;
useEffect

useEffect 包含以下知识点:

  • 副作用的概念

    • 纯函数:一个确切的参数在你的函数中运行后,一定能得到一个确切的值,例如下面的例子:
    js 复制代码
    function test(x){
      return x * 2;
    }
    
    x => 2 ===> 4
    x => 3 ===> 6
    • 如果一个函数中,存在副作用,那么我们就称该函数不是一个纯函数,所谓副作用,就是指函数的结果是不可控,不可预期。
    • 常见的副作用有发送网络请求、添加一些监听的注册和取消注册,手动修改 DOM ,以前我们是将这些副作用写在生命周期钩子函数里面,现在就可以书写在 useEffect 这个 Hook 里面
javascript 复制代码
import { useState, useEffect } from 'react';

function App() {

  let [count1, setCount1] = useState(0);
  let [count2, setCount2] = useState(0);
  let [count3, setCount3] = useState(0);

  useEffect(()=>{
    console.log("执行副作用函数");
  },[count1]);

  return (
    <div>
      <div>count1:{count1}</div>
      <div>count2:{count2}</div>
      <div>count3:{count3}</div>
      <button onClick={()=>setCount1(++count1)}>+1</button>
      <button onClick={()=>setCount2(++count2)}>+1</button>
      <button onClick={()=>setCount3(++count3)}>+1</button>
    </div>
  );
}

export default App;
  • 如果想要副作用只执行一次,传递第二个参数为一个空数组
js 复制代码
useEffect(()=>{
  console.log("执行副作用函数");
},[]);

2、自定义Hooks

自定义 Hook 的本质其实就是函数,但是和普通函数还是有一些区别,主要体现在以下两个点:

  • 自定义 Hook 能够调用诸如 useStateuseRef 等,普通函数则不能。由此可以通过内置的 Hooks 获得 Fiber 的访问方式,可以实现在组件级别存储数据的方案等。
  • 自定义 Hooks 需要以 use 开头,普通函数则没有这个限制。使用 use 开头并不是一个语法或者一个强制性的方案,更像是一个约定。

App.jsx

js 复制代码
import { useState } from 'react';
import useMyBook from "./useMyBook"

function App() {

  const {bookName, setBookName} = useMyBook();
  const [value, setValue] = useState("");


  function changeHandle(e){
    setValue(e.target.value);
  }

  function clickHandle(){
    setBookName(value);
  }

  return (
    <div>
      <div>{bookName}</div>
      <input type="text" value={value} onChange={changeHandle}/>
      <button onClick={clickHandle}>确定</button>
    </div>
  )
  
}

export default App;

useMyBook

js 复制代码
import { useState } from "react";

function useMyBook(){
    const [bookName, setBookName] = useState("React 学习");
    return {
        bookName, setBookName
    }
}

export default useMyBook;

5、React-router 路由

React-routerReact 官方所推出的前端路由库,可以分为几大块:

  • Components 组件
  • Hooks 函数
  • API 函数

组件

  • BrowserRouter:整个前端路由以 history 模式开始,包裹根组件
  • HashRouter:整个前端路由以 hash 模式开始,包裹根组件
  • Routes:类似于 v5 版本的 Switch,主要是提供一个上下文环境
  • Route:在 Route 组件中书写你对应的路由,以及路由所对应的组件
    • path:匹配的路由
    • element:匹配上该路由时,要渲染的组件
  • Navigate:导航组件,类似于 useNavigate 的返回值函数,只不过这是一个组件
  • NavLink:类似于 Link,最终和 Link 一样,会被渲染为 a 标签,注意它和 Link 的区别,实际上就是当前链接,会有一个名为 active 的激活样式,所以一般用于做顶部或者左侧导航栏的跳转

Hooks

  • useLocation:获取到 location 对象,获取到 location 对象后,我们可以获取 state 属性,这往往是其他路由跳转过来的时候,会在 state 里面传递额外的数据
  • useNavigate:调用之后会返回一个函数,通过该函数可以做跳转。
  • useParams:获取动态参数
javascript 复制代码
import { useRoutes, Navigate } from "react-router-dom";
import Home from "../components/Home";
import About from "../components/About";
import AddOrEdit from "../components/AddOrEdit";
import Detail from "../components/Detail";
import Email from "../components/Email";
import Tel from "../components/Tel";

function Router(props) {
    return useRoutes([
        {
            path: "/home",
            element: <Home />,
        },
        {
            path: "/about",
            element: <About />,
            children : [
		         {
		           path : "email",
		           element : <Email/>
		         },
		         {
		           path : "tel",
		           element : <Tel/>
		         },
		         {
		           path : "",
		           element: <Navigate replace to="email" />
		         }
		       ]
        },
        {
            path: "/add",
            element: <AddOrEdit />,
        },
        {
            path: "/detail/:id",
            element: <Detail />,
        },
        {
            path: "/edit/:id",
            element: <AddOrEdit />,
        },
        {
            path: "/",
            element: <Navigate replace to="/home" />
        }
    ]);
}

export default Router;
javascript 复制代码
import { NavLink } from "react-router-dom";
import RouterConfig from "./router/router"

import "./css/App.css"

function App() {
  return (
    // 最外层容器
    <div id="app" className="container">
      {/* 导航栏 */}
      <nav className="navbar navbar-inverse navbar-fixed-top">
        <div className="container">
          <div className="navbar-header">
            <button type="button" className="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
              <span className="sr-only">Toggle navigation</span>
              <span className="icon-bar"></span>
              <span className="icon-bar"></span>
              <span className="icon-bar"></span>
            </button>
            <div className="navbar-brand">学生管理系统</div>
          </div>
          <div id="navbar" className="collapse navbar-collapse">
            <ul className="nav navbar-nav">
              <NavLink to="/home" className="navigation">主页</NavLink>
              <NavLink to="/about" className="navigation">关于我们</NavLink>
            </ul>
            <ul className="nav navbar-nav navbar-right">
              <NavLink to="/add" className="navigation">添加学生</NavLink>
            </ul>
          </div>
        </div>
      </nav>
      {/* 匹配上的路由所对应的组件显示在这个位置 */}
      <div className="content">
        <RouterConfig/>
      </div>

    </div>
  );
}

export default App;

使用 Outlet 组件,该组件表示匹配上的子路由组件渲染的位置。

javascript 复制代码
import React from 'react';
import { NavLink, Outlet } from "react-router-dom"

function About() {
    return (
        <div className="about container">
            <h1 className="page-header">使用说明</h1>
            <p>通过此系统来熟悉 react 以及 react router 的使用</p>
            <p>联系方式</p>
            <NavLink to="/about/email" className="navigation">邮箱</NavLink>
            <NavLink to="/about/tel" className="navigation">电话</NavLink>
            <Outlet />
        </div>
    );
}

export default About;

6、React-redux

React 官方在 Redux 的基础上推出了 React-redux ,另外Redux 官方还推出了 Redux Toolkit ,来简化整个 Redux 的使用。因此现在在 React 应用中,状态管理库的使用一般都是 React-redux + Redux Toolkit

首先第一个安装两个依赖,命令如下:

js 复制代码
npm install @reduxjs/toolkit react-redux

状态管理

所谓状态管理,指的是把组件之间需要共享的状态抽取出来,遵循特定的约定,统一来管理,让状态的变化可以预测

  • 单向数据流View 发出 Action (store.dispatch(action)),Store 调用 Reducer 计算出新的 state ,若 state 产生变化,则调用监听函数重新渲染 View (store.subscribe(render)

  • 单一数据源 ,只有一个 Store

  • state 是只读的,每次状态更新之后只能返回一个新的 state

  • 没有 Dispatcher ,而是在 Store 中集成了 dispatch 方法,store.dispatch()View 发出 Action 的唯一途径

  • 支持使用中间件(Middleware)管理异步数据流

Redux 数据流:

在浏览器中使用 redux 扩展工具,首先需要打开谷歌浏览器的扩展应用商店,下载 redux devtools

接下来需要在创建 store 的地方,添加如下的代码:

js 复制代码
export const store = createStore(
    todoReducer,
  + window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

React-redux原生写法

配置:

store.js

javascript 复制代码
import { createStore } from "redux";
// 引入 reducer
import { todoReducer } from "./reducers"
// 需要你传入一个 reducer(纯函数,用于计算最新的状态)
export const store = createStore(
    todoReducer,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

reducers.js

javascript 复制代码
// reducer,用于计算最新的状态
import { ADD,DEL,CHANGE } from "./actionType";
// 仓库一开始默认的数据
let defaultState = {
  list: [
    {
      content: "学习 React",
      status: false,
    },
    {
      content: "复习 Vue",
      status: false,
    },
    {
      content: "玩游戏",
      status: false,
    },
    {
      content: "听歌",
      status: false,
    },
  ],
};

/**
 * 通过这个纯函数我们会计算出最新的状态
 * state : 仓库数据,每次会传入上一次的仓库数据
 * action : 描述对象 {type : ADD, data : "学习 redux"}
 * 描述对象描述了我要做什么,以及带来的额外数据
 */
export function todoReducer(state = defaultState, action) {
  // 有了描述对象后,我就可以根据旧状态计算出新的状态并返回
  switch (action.type) {
    case ADD: {
      // 新增的操作
      const arr = [...state.list];
      arr.push({
        content: action.data,
        status: false,
      });
      return { list: arr };
    }
    case DEL : {
       const arr = [...state.list];
       arr.splice(action.data, 1);
       return { list: arr };
    }
    case CHANGE : {
        const arr = [...state.list];
        arr[action.data].status = !arr[action.data].status;
        return { list: arr };
    }
    default: return state;
  }
}

action.js

javascript 复制代码
export const addListAction = newItem => ({
    type: ADD,
    data : newItem
})

export const delListAction = index => ({
    type: DEL,
    data : index
})

export const changeAction = index => ({
    type: CHANGE,
    data : index
})

使用:

index.js

javascript 复制代码
import ReactDOM from 'react-dom/client';
import App from './App';

// 引入仓库
import { store } from "./redux/store";

const root = ReactDOM.createRoot(document.getElementById('root'));

function render(){
    root.render(
        <App store={store}/>
    );
}
render();


// 让 redux 和 react 建立关联
// subscribe 叫做订阅,在仓库的状态发生改变的时候
store.subscribe(render);

app.js

javascript 复制代码
import Input from "./components/Input";
import List from "./components/List";

import "./css/App.css"

function App(props) {

  return (
    // 最外层容器
    <div className="container">
      <h1 className="lead" style={{
        marginBottom : "30px"
      }}>待办事项</h1>
      <Input store={props.store}/>
      <List store={props.store}/>
    </div>
  )

}

export default App;

List.jsx

javascript 复制代码
import {delListAction,changeAction} from "../redux/actions"

function List(props) {
    // 在 redux,通过 store.getState() 来获取仓库数据
    // dispatch 方法会派发一个 action 对象到 reducer 里面
    // addListAction(value) ===> { type : ADD, data : value} 
    // 这个就是我们的 action 描述对象,该对象会被 dispatch(派发)到 reducer 里面
    const lis = props.store.getState().list.map((item,index)=>{
        return (
            <li key={index} className="text-primary">
                <span
                    onClick={()=>props.store.dispatch(changeAction(index))}
                    className={["item", item.status ? 'completed' : ""].join(" ")}
                >{item.content}</span>
                <button
                    type="button"
                    className='close'
                    onClick={()=>props.store.dispatch(delListAction(index))}
                >&times;</button>
            </li>
        )
    })

    return (
        <div>
            <ul style={{marginTop : 20}}>
                {lis}
            </ul>
        </div>
    );
}

export default List;

React-redux + Redux Toolkit 写法

配置:

store.js

javascript 复制代码
import { configureStore } from "@reduxjs/toolkit";
import stuReducer from "./stuSlice";

export default configureStore({
  reducer: {
    stu: stuReducer,
  },
});

stuSlice.js

javascript 复制代码
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import {
  getStuListApi,
  addStuApi,
} from "../api/stuApi";

// 异步获取所有的学生数据
export const getStuListAsync = createAsyncThunk(
  "stu/getStuListAsync",
  async (_, thunkApi) => {
    // 发送 ajax 请求
    const response = await getStuListApi();
    // 派发 action
    thunkApi.dispatch(initStuList(response.data));
  }
);

// 异步新增学生
export const addStuAsync = createAsyncThunk(
  "stu/addStuAsync",
  async (payload, thunkApi) => {
    const { data } = await addStuApi(payload);
    // 将这个 data 更新到数据仓库
    thunkApi.dispatch(addStu(data));
  }
);


export const stuSlice = createSlice({
  name: "stu",
  initialState: {
    stuList: [],
  },
  reducers: {
    // 初始化学生列表到仓库的 stuList 里面
    initStuList: (state, { payload }) => {
      state.stuList = payload;
    },
    // 添加学生
    addStu: (state, { payload }) => {
      state.stuList.push(payload);
    },
  },
});

const { initStuList, addStu } = stuSlice.actions;
export default stuSlice.reducer;

使用

index.js

javascript 复制代码
import ReactDOM from "react-dom/client";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import store from "./redux/store";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <Provider store={store}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </Provider>
);

Home.jsx

javascript 复制代码
import { useSelector, useDispatch } from "react-redux";
import { getStuListAsync } from "../redux/stuSlice"

function Home(props) {
    // 现在应该是从仓库里面去获取学生的数据
    const { stuList } = useSelector(state => state.stu);
    const dispatch = useDispatch();

    // 注意,这里需要添加依赖性为空数组,代表只执行一次
    useEffect(() => {
        // 现在应该是从仓库获取数据
        if(!stuList.length){
            // 如果没有数据,应该发送请求获取数据
            // 但是也不是在这里直接发请求,而是应该派发一个 action 到仓库
            // 仓库里面负责发送异步请求获取数据,然后将获取到的数据填充到前端仓库
            dispatch(getStuListAsync());
        }
    }, [stuList, dispatch]);
 }

Add.jsx

javascript 复制代码
import { addStuAsync, editStuAsync } from "../redux/stuSlice";
import { useSelector, useDispatch } from "react-redux"

const dispatch = useDispatch();
const { stuList } = useSelector(state => state.stu);

const [stu, setStu] = useState({
    name: "",
    age: "",
    phone: "",
    email: "",
    education: "本科",
    graduationschool: "",
    profession: "",
    profile: "",
});
function submitStuInfo(e) {
        e.preventDefault();
        for (const key in stu) {
            if (!stu[key]) {
                alert("请完善表单的每一项");
                return;
            }
        }
        if (id) {
            // 编辑
            dispatch(editStuAsync({ id, stu }));
            navigate("/home", {
                state: {
                    alert: "学生修改成功",
                    type: "info",
                }
            });

        } else {
            dispatch(addStuAsync(stu));
            navigate("/home", {
                state: {
                    alert: "学生添加成功",
                    type: "success",
                }
            });
        }
    }

区别

index.js 的变化,需要从 react-redux 中引入 Provider 的组件,用于提供一个上下文环境,包裹应用的根组件,之后仓库会做为 Provider 的 store 属性,不需要再在 App.jsx 根组件上面挂载了

js 复制代码
// ....
import { Provider } from "react-redux";

// 引入仓库
import store from "./redux/store";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

store.js 的变化,从 toolkit 里面引入 configureStore 方法,用于创建我们的数据仓库

js 复制代码
// 引入创建仓库的方法
import { configureStore } from "@reduxjs/toolkit";
// 调用该方法时,传入一个配置对象
// 其中一个选项是配置 reducer
export default configureStore({
    reducer : {

    }
});

组件连接仓库的改变,之前使用 redux 的时候,组件还是需要从父组件传递的 props 上面拿到仓库数据,现在可以通过 useSelector 这个 Hook 直接连接仓库

js 复制代码
const {list} = useSelector(state=>state.todo);

组件向仓库派发 action 时的改变,首先是获取 dispatch 方法的方式,之前使用纯 redux 的时候,dispatch 是通过 store 拿到的,现在是通过 useDispatch 来拿到。

js 复制代码
 dispatch(add(value));

action 之前是通过我们自己书写的 action creator 来创建的,现在是直接从 slice 里面导出即可。

js 复制代码
export const {add,del,change} = todolistSlice.actions;
相关推荐
whuhewei2 小时前
在React中实现CSS动画的回放
前端·css·react.js
北海军2 小时前
render el-select下拉框
前端·javascript·vue.js
We་ct2 小时前
LeetCode 4. 寻找两个正序数组的中位数:二分优化思路详解
前端·数据结构·算法·leetcode·typescript·二分
H@Z*rTE|i2 小时前
vue首屏加载优化
前端·javascript·vue.js
FreeBuf_2 小时前
新型开源供应链攻击:虚假 npm 安装日志暗藏 RAT 木马
前端·npm·开源
Irene19912 小时前
v-model 的本质,defineModel() 是 Vue 3.4 的重大改进
前端·javascript·html5
EF@蛐蛐堂3 小时前
【vue】Vite 生态 5 个 “新玩具“
前端·javascript·vue.js
风之舞_yjf3 小时前
Vue基础(29)_props配置项、ref属性
前端·vue.js
Fairy要carry3 小时前
项目03-手搓Agent之团队协作(发消息/分配任务)
linux·前端·python