React前端框架


一、React介绍

1.1 简介

React 是由 Meta(前 Facebook) 开发并开源的一个用于构建用户界面的 JavaScript 库,(可用于构建Web和原生交互界面)。它主要用于创建单页应用(SPA, Single Page Application),能够通过组件化的开发模式构建复杂的用户界面。

React 是现代前端开发中最流行的工具之一,凭借其高效的虚拟 DOM、组件化开发模式和强大的生态系统,成为构建用户界面的首选技术。无论是中小型项目还是复杂的大型应用,React 都能提供优雅的解决方案。

1.2 优势

相较于传统基于DOM开发的优势:

  1. 组件化的开发方式
  2. 不错的性能

相较于其它前端框架的优势

  1. 丰富的生态
  2. 跨平台支持

1.3 React的市场情况

全球最流行,大厂必备。

二、开发环境搭建

2.1 初始化react工程项目

create-react-app是一个快速创建React开发环境的工具,底层由Webpack构件,封装了配置细节,开箱即用。

执行命令:

npx create-react-app react-basic
  1. npx - Node.js工具命令,查找并执行后续的包命令
  2. create-react-app - 核心包(固定写法),用于创建React项目
  3. react-basic React项目的名称(可以自定义)
cd react-basic
npm start

bug解决:

npm install ajv@latest ajv-keywords@latest

测试:npm start

2.2 关键文件

2.2.1 package.json

{
  "dependencies": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
  },

react基础项目的核心依赖和核心脚本。

2.2.2 src源码目录

除了高亮的两个文件,其他都可以删除。

  • index.js清理

    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import './index.css';
    import App from './App';
    import reportWebVitals from './reportWebVitals';
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
      <React.StrictMode>
        <App />
      </React.StrictMode>
    );
    
    // If you want to start measuring performance in your app, pass a function
    // to log results (for example: reportWebVitals(console.log))
    // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
    reportWebVitals();
    

    清理后:

    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import App from './App';
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(<App />);
  • App.js清理

import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

清理后:

function App() {
  return (
    <div className="App">
        This is React App.
    </div>
  );
}

export default App;

访问测试:

2.3 渲染逻辑

2.3.1 入口文件:index.js

//项目的入口 从这里开始运行

//react必要的两个核心包
import React from 'react';
import ReactDOM from 'react-dom/client';

//导入项目的根组件
import App from './App';

//把App根组件渲染到id为root的dom节点上
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

2.3.2 id为root的dom节点

2.3.3 根组件

//项目的根组件
function App() {
  return (
    <div className="App">
        This is React App.
    </div>
  );
}

export default App;

2.3.4 渲染流程

App根组件->index.js->public/index.html(root)

三、JSX基础

3.1 什么是JSX

JSX是JavaScript和XMl(HTML)的缩写,表示在JS代码中编写HTML模版结构,它是React中构建UI的方式。

优势:结合HTML+JS

  1. HTML的声明式模版写法
  2. JavaScript的可编程能力
const message = 'this is message'

function App(){
  return (
    <div>
      <h1>this is title</h1>
      {message}
    </div>
  )
}

3.2 JSX的本质

JSX并不是标准的JS语法,它是 JS的语法扩展,浏览器本身不能识别,需要通过解析工具做解析之后才能在浏览器中使用。

Babel在线网站,在线将JSX编译成JS。

3.3 JSX高频场景-JS表达式

在JSX中可以通过 大括号语法{} 识别JavaScript中的表达式,比如常见的变量、函数调用、方法调用等等。

  1. 使用引号传递字符串
  2. 使用JS变量
  3. 函数调用和方法调用
  4. 使用JavaScript对象

注意:if语句、switch语句、变量声明不属于表达式,不能出现在{}中。

//项目的根组件
function helloJsx(){
  return "Hello Jsx!"
}
function App() {
  const count=99999
  return (
    <div className="App">
        This is React App.
        {/* 使用引号传递字符串 */}
        {'This is a String'}
        {/* 使用JS变量 */}
        {count}
        {/*函数调用和方法调用*/}
        {helloJsx()}
        {new Date().getDate()}
        {/* 使用JavaScript对象 */}
        <div style={{color:'red',fontSize:50}}>我变红,变大了吗?</div>
    </div>
  );
}
export default App;

截屏2024-12-10 15.35.34

3.4 JSX高频场景-列表渲染

在JSX中可以使用原生js种的map方法 实现列表渲染。

const list = [
  {id:1001, name:'Vue'},
  {id:1002, name: 'React'},
  {id:1003, name: 'Angular'}
]

function App(){
  return (
    <ul>
      {/* 渲染列表 */}
      {/* map 循环哪个结构 return结构*/}
      {/* 注意事项:加上一个独一无二的key,字符串或者number idt*/}
      {/* key的作用:React框架内部使用 提升更新性能的 */}
      {list.map(item=><li key={item.id}>{item.name}</li>)}
    </ul>
  )
}

js中的箭头函数:

在JavaScript中,箭头函数(Arrow Function)是ES6(ECMAScript 2015)引入的一种新的函数写法,它提供了一种更简洁的方式来写函数,箭头函数的语法比传统的函数表达式更短,这些函数表达式更适用于那些函数体较短且不需要使用 function 关键字的场景。

箭头函数的基本语法如下:

(param1, param2, ..., paramN) => {
  // 函数体
}

如果函数体只有一条语句,可以省略花括号,并且隐式返回该语句的结果:

(param1, param2, ..., paramN) => expression

如果只有一个参数,可以省略参数周围的括号:

param => expression

没有参数的函数需要使用一对空括号:

() => expression

3.5 条件渲染

3.5.1 基础条件渲染

const flag = true
const loading = false

function App(){
  return (
    <div>
      {flag && <span>this is span</span>}
      {loading ? <span>loading...</span>:<span>this is span</span>}
    </div>
  )
}
  • 一种显示的情况:用&&运算符

  • 两种显示的情况:三元运算符

  • 三种显示的情况:if else

3.5.2 复杂条件渲染

  • 需求:列表中需要根据文章的状态适配
  • 解决方案:自定义函数 + 判断语句
const articleType=1 // 0无图 1单图 2双图

function getArticleTmpl(){
  if(articleType===0){
    return <div>无图模式</div>
  }else if(articleType===1){
    return <div>单图模式</div>
  }else if(articleType===2){
    return <div>双图模式</div>
  }else{
    return <div style={{color:"red"}}>参数错误!</div>
  }
}

function App() {
  return (
    <div className="App">
        {getArticleTmpl()}
    </div>
  );
}
export default App;

四、React中的事件绑定

4.1 基础事件绑定

React中的事件绑定,通过语法 on + 事件名称 = { 事件处理程序 },整体上遵循驼峰命名法。

function App() {
  const btnClickHandler = ()=>{
    alert("按钮被点击了!")
  }
  return (
    <div className="App">
        {/* 基础事件绑定 */}
        <hr></hr>
        <button onClick={btnClickHandler}>点我</button> //不用传参的情况
    </div>
  );
}
export default App;

4.2 使用事件参数

在事件回调函数中设置形参e即可。

function App(){
  const clickHandler = (e)=>{
    console.log('button按钮点击了', e)
  }
  return (
    <button onClick={clickHandler}>click me</button>
  )
}

事件对象e:

e 是一个参数,它代表事件对象(Event object)。当你在 React 组件中使用 onClick 属性时,浏览器会自动将生成的事件对象作为参数传递给绑定的事件处理函数。

具体来说,当你点击 <button> 元素时,会触发一个点击事件(click event),浏览器会创建一个事件对象 e,其中包含了关于这个事件的详细信息,比如:

  • e.type:事件的类型,这里是 "click"
  • e.target:触发事件的元素,这里是 <button> 元素。
  • e.currentTarget:事件绑定的元素,如果事件被冒泡,这里可能与 e.target 不同。
  • e.preventDefault():一个方法,用于阻止事件的默认行为。
  • e.stopPropagation():一个方法,用于阻止事件继续传播到父元素。
  • e.clientXe.clientY:鼠标点击时的位置坐标。
  • 等等。

4.3 传递自定义参数

语法:事件绑定的位置改造成箭头函数的写法,在执行clickHandler实际处理业务函数的时候传递实参。

注意:不能直接写函数调用,这里事件绑定需要一个函数引用。

4.4 同时传递事件对象和自定义参数

语法:在事件绑定的位置传递事件实参e和自定义参数,clickHandler中声明形参,注意顺序对应。

function App(){
  const clickHandler = (name,e)=>{
    console.log('button按钮点击了', name,e)
  }
  return (
    <button onClick={(e)=>clickHandler('jack',e)}>click me</button>
  )
}

五、React组件

5.1 组件是什么

概念:一个组件就是一个用户界面的一部分,它可以有自己的逻辑和外观,组件之间可以互相嵌套,也可以复用多次。

组件化开发可以让开发者像搭积木一样构建一个完整的庞大的应用。

5.2 组件基础使用

在React中,一个组件就是首字母大写的函数,内部存放了组件的逻辑和视图UI, 渲染组件只需要把组件当成标签书写即可。

// 1. 定义组件
function Button(){
  // 内部逻辑
  return <button>click me</button>
}

// 可以用箭头函数的方法
// const MyButton2 = ()=>{
//   return <button>Click Me 2</button>  
// }


// 2. 使用组件
function App(){
  return (
    <div>
      {/* 自闭和 */}
      <Button/>
      {/* 成对标签 */}
      <Button></Button>
    </div>
  )
}

六、useState

6.1 基础使用

useState 是一个 React Hook(函数),它允许我们向组件添加一个状态变量, 从而控制影响组件的渲染结果。

和普通JS变量不同的是,状态变量一旦发生变化组件的视图UI也会跟着变化(数据驱动视图)。

// useState实现一个计数器按钮
import { useState } from 'react'
function App () {
  // 1. 调用useState添加一个状态变量
  // count 状态变量
  // setCount 修改状态变量的方法
  const [count, setCount] = useState(0)

  // 2. 点击事件回调
  const handleClick = () => {
    // 作用: 
    // 1. 用传入的新值修改count
    // 2. 重新使用新的count渲染UI
    setCount(count + 1)
  }
  return (
    <div>
      <button onClick={handleClick}>{count}</button>
    </div>
  )
}
export default App

6.2 状态的修改规则

在React中状态被认为是只读的,我们应该始终替换它而不是修改它, 直接修改状态不能引发视图更新。

6.3 修改对象的状态

对于对象类型的状态变量,应该始终给set方法一个全新的对象 来进行修改。

import { useState } from 'react'

function App () {
  let [count, setCount] = useState(0)

  const handleClick = () => {
    // 直接修改 无法引发视图更新
    // count++
    // console.log(count)
    setCount(count + 1)
  }

  // 修改对象状态
  const [form, setForm] = useState({ name: 'jack' })

  const changeForm = () => {
    // 错误写法:直接修改
    // form.name = 'john'
    // 正确写法:setFrom 传入一个全新的对象
    setForm({
      ...form,
      name: 'john'
    })
  }

  return (
    <div>
      <button onClick={handleClick}>{count}</button>
      <button onClick={changeForm}>修改form{form.name}</button>
    </div>
  )
}

export default App

七、样式控制

7.1 行内样式(不推荐)

7.2 类名控制样式(单独css文件)

// 导入样式
import './index.css'

const style = {
  color: 'red',
  fontSize: '50px'
}

function App () {
  return (
    <div>
      {/* 行内样式控制 */}
      <span style={style}>this is span</span>
      {/* 通过class类名控制 */}
      <span className="foo">this is class foo</span>
    </div>
  )
}

export default App
.foo {
  color: blue;
}

八、classnames动态控制类名

8.1 安装classnames库

classnames是一个简单的JS库,可以非常方便的通过条件动态控制class类名的显示。

npm install classnames

8.2 使用

原始的模版字符串方式

问题:字符串的拼接方式不够直观,也容易出错。

调用classNames()函数

九、受控表单绑定

概念:使用React组件的状态(useState)控制表单的状态。

1)准备一个React状态值

const [value, setValue] = useState('')

2)通过value属性绑定状态,通过onChange属性绑定状态同步的函数

// 受控绑定表单

import { useState } from "react"

// 1. 声明一个react状态 - useState

// 2. 核心绑定流程
// 1. 通过value属性绑定react状态
// 2. 绑定onChange事件 通过事件参数e拿到输入框最新的值 反向修改到react状态身上

function App () {
  const [value, setValue] = useState('')
  return (
    <div>
      <input
        value={value}
        onChange={(e) => setValue(e.target.value)}
        type="text" />
    </div>
  )
}

export default App

十、React获取DOM元素

在 React 组件中获取/操作 DOM,需要使用 useRef React Hook钩子函数,分为两步:

  1. 使用useRef创建 ref 对象,并与 JSX 绑定
  1. 在DOM可用时,通过 inputRef.current 拿到 DOM 对象

十一、组件通信

11.1 组件通信的概念

概念:组件通信就是组件之间的数据传递,根据组件嵌套关系的不同,有不同的通信方法。

截屏2024-12-17 17.41.28

11.2 父子组件通信

11.2.1 父传子

实现步骤:

  1. 父组件传递数据 —— 在子组件标签上绑定属性。
  2. 子组件接收数据 —— 子组件通过props参数接收数据。
// 父传子
// 1. 父组件传递数据  子组件标签身上绑定属性
// 2. 子组件接收数据  props的参数

function Son (props) {
  // props:对象里面包含了父组件传递过来的所有的数据
  // { name:'父组件中的数据'}
  console.log(props)
  return <div>this is son, {props.name}, jsx: {props.child}</div>
}

function App () {
  const name = 'this is app name'
  return (
    <div>
      <Son
        name={name}
        age={18}
        isTrue={false}
        list={['vue', 'react']}
        obj={{ name: 'jack' }}
        cb={() => console.log(123)}
        child={<span>this is span</span>}
      />
    </div>
  )
}

export default App
  • props说明
  1. props可传递任意的数据

​ 数字、字符串、布尔值、数组、对象、函数、JSX

  1. props是只读对象

    子组件只能读取props中的数据,不能直接进行修改, 父组件的数据只能由父组件修改

  2. 特殊的prop children

​ 场景:当我们把内容嵌套在子组件标签中时,父组件会自动在名为children的prop属性中接收该内容.

截屏2024-12-17 18.08.18

11.3 子传父

核心思路:在子组件中调用父组件中的函数并传递参数。

// 核心:在子组件中调用父组件中的函数并传递实参

import { useState } from "react"


function Son ({ onGetSonMsg }) {
  // Son组件中的数据
  const sonMsg = 'this is son msg'
  return (
    <div>
      this is Son
      <button onClick={() => onGetSonMsg(sonMsg)}>sendMsg</button>
    </div>
  )
}

function App () {
  const [msg, setMsg] = useState('')
  const getMsg = (msg) => {
    console.log(msg)
    setMsg(msg)
  }
  return (
    <div>
      this is App, {msg}
      <Son onGetSonMsg={getMsg} />
    </div>
  )
}

export default App

11.4 兄弟组件通信

实现思路:借助“状态提升”机制,通过父组件进行兄弟组件之间的数据传递

  1. A组件先通过子传父的方式把数据传给父组件App
  2. App拿到数据后通过父传子的方式再传递给B组件

// 1. 通过子传父 A -> App
// 2. 通过父传子 App -> B

import { useState } from "react"

function A ({ onGetAName }) {
  // Son组件中的数据
  const name = 'this is A name'
  return (
    <div>
      this is A compnent,
      <button onClick={() => onGetAName(name)}>send</button>
    </div>
  )
}

function B ({ name }) {
  return (
    <div>
      this is B compnent,
      {name}
    </div>
  )
}

function App () {
  const [name, setName] = useState('')
  const getAName = (name) => {
    console.log(name)
    setName(name)
  }
  return (
    <div>
      this is App
      <A onGetAName={getAName} />
      <B name={name} />
    </div>
  )
}

export default App

11.5 跨层级组件通信

使用Context机制跨层级组件通信。

实现步骤:

  1. 使用createContext方法创建一个上下文对象Ctx。
  2. 在顶层组件(App)中通过 Ctx.Provider 组件提供数据。
  3. 在底层组件(B)中通过 useContext 钩子函数获取消费数据。
// App -> A -> B

import { createContext, useContext } from "react"

// 1. createContext方法创建一个上下文对象

const MsgContext = createContext()

// 2. 在顶层组件 通过Provider组件提供数据

// 3. 在底层组件 通过useContext钩子函数使用数据

function A () {
  return (
    <div>
      this is A component
      <B />
    </div>
  )
}

function B () {
  const msg = useContext(MsgContext)
  return (
    <div>
      this is B compnent,{msg}
    </div>
  )
}

function App () {
  const msg = 'this is app msg'
  return (
    <div>
      <MsgContext.Provider value={msg}>
        this is App
        <A />
      </MsgContext.Provider>
    </div>
  )
}

export default App

只要存在顶层和底层的关系都可以使用Context来进行通信。

十二、useEffect

12.1 useEffect 的概念理解

useEffect是一个React Hook函数,用于在React组件中创建不是由事件引起而是由渲染本身引起的操作(副作用), 比如发送AJAX请求,更改DOM等等。

说明:上面的组件中没有发生任何的用户事件,组件渲染完毕之后就需要和服务器要数据,整个过程属于“只由渲染引起的操作”。

12.2 useEffect 的基础使用

需求:在组件渲染完毕之后,立刻从服务端获取频道列表数据并显示到页面中。

语法:

参数1是一个函数,可以把它叫做副作用函数,在函数内部可以放置要执行的操作。

参数2是一个数组(可选参),在数组里放置依赖项,不同依赖项会影响第一个参数函数的执行,当是一个空数组的时候,副作用函数只会在组件渲染完毕之后执行一次。

接口地址:http://geek.itheima.net/v1_0/channels

import { useEffect, useState } from "react"

const URL = 'http://geek.itheima.net/v1_0/channels'

function App () {
  // 创建一个状态数据
  const [list, setList] = useState([])
  useEffect(() => {
    // 额外的操作 获取频道列表
    async function getList () {
      const res = await fetch(URL)
      const jsonRes = await res.json()
      console.log(jsonRes)
      setList(jsonRes.data.channels)
    }
    getList()
  }, [])
  return (
    <div>
      this is app
      <ul>
        {list.map(item => <li key={item.id}>{item.name}</li>)}
      </ul>
    </div>
  )
}

export default App

12.3 useEffect 依赖项参数说明

useEffect副作用函数的执行时机存在多种情况,根据传入依赖项的不同,会有不同的执行表现:

import { useEffect, useState } from "react"

function App () {
  // 1. 没有依赖项  初始 + 组件更新(例如状态变量更新)
  const [count, setCount] = useState(0)
  // useEffect(() => {
  //   console.log('副作用函数执行了')
  // })

  // 2. 传入空数组依赖  初始执行一次
  // useEffect(() => {
  //   console.log('副作用函数执行了')
  // }, [])

  // 3. 传入特定依赖项  初始 + 依赖项变化时执行
  useEffect(() => {
    console.log('副作用函数执行了')
  }, [count])

  return (
    <div>
      this is app
      <button onClick={() => setCount(count + 1)}>+{count}</button>
    </div>
  )
}

export default App

12.4 清除副作用

在useEffect中编写的由渲染本身引起的对接组件外部的操作,社区也经常把它叫做副作用操作,比如在useEffect中开启了一个定时器,我们想在组件卸载时把这个定时器再清理掉,这个过程就是清理副作用。

  • 说明:清除副作用的函数最常见的执行时机是在组件卸载时自动执行。

  • 需求:在Son组件渲染时开启一个定制器,卸载时清除这个定时器。

import { useEffect, useState } from "react"

function Son () {
  // 1. 渲染时开启一个定时器
  useEffect(() => {
    const timer = setInterval(() => {
      console.log('定时器执行中...')
    }, 1000)

    return () => {
      // 清除副作用(组件卸载时)
      clearInterval(timer)
    }
  }, [])
  return <div>this is son</div>
}

function App () {
  // 通过条件渲染模拟组件卸载
  const [show, setShow] = useState(true)
  return (
    <div>
      {show && <Son />}
      <button onClick={() => setShow(false)}>卸载Son组件</button>
    </div>
  )
}

export default App

十三、自定义Hook函数

13.1 自定义Hook函数基础用法

概念:自定义Hook是以 use 打头的函数,通过自定义Hook函数可以用来实现逻辑的封装和复用。

// 封装自定义Hook

// 问题: 布尔切换的逻辑 当前组件耦合在一起的 不方便复用

// 解决思路: 自定义hook

import { useState } from "react"

function useToggle () {
  // 可复用的逻辑代码
  const [value, setValue] = useState(true)

  const toggle = () => setValue(!value)

  // 哪些状态和回调函数需要在其他组件中使用 return
  return {
    value,
    toggle
  }
}

// 封装自定义hook通用思路
// 1. 声明一个以use打头的函数
// 2. 在函数体内封装可复用的逻辑(只要是可复用的逻辑)
// 3. 把组件中用到的状态或者回调return出去(以对象或者数组)
// 4. 在哪个组件中要用到这个逻辑,就执行这个函数,解构出来状态和回调进行使用

function App () {
  const { value, toggle } = useToggle()
  return (
    <div>
      {value && <div>this is div</div>}
      <button onClick={toggle}>toggle</button>
    </div>
  )
}

export default App

13.2 ReactHooks使用规则

使用规则

  1. 只能在组件中或者其他自定义Hook函数中调用。
  2. 只能在组件的顶层调用,不能嵌套在 if、for、其他函数中。

十四、Redux+React

14.1 Redux快速上手

14.1.1 什么是Redux?

Redux 是React最常用的集中状态管理工具,类似于Vue中的Pinia(Vuex),可以独立于框架运行。

作用:通过集中管理的方式管理应用的状态。

14.1.2 Redux快速体验

不和任何框架绑定,不使用任何构建工具,使用纯Redux实现计数器。

使用步骤:

  1. 定义一个 reducer 函数 (根据当前想要做的修改返回一个新的状态)。
  2. 使用createStore方法传入 reducer函数 生成一个store实例对象。
  3. 使用store实例的 subscribe方法 订阅数据的变化(数据一旦变化,可以得到通知)。
  4. 使用store实例的 dispatch方法提交action对象触发数据变化(告诉reducer你想怎么改数据)。
  5. 使用store实例的 getState方法 获取最新的状态数据更新到视图中。
<button id="decrement">-</button>
<span id="count">0</span>
<button id="increment">+</button>

<script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>

<script>
  // 1. 定义reducer函数 
  // 作用: 根据不同的action对象,返回不同的新的state
  // state: 管理的数据初始状态
  // action: 对象 type 标记当前想要做什么样的修改
  function reducer (state = { count: 0 }, action) {
    // 数据不可变:基于原始状态生成一个新的状态
    if (action.type === 'INCREMENT') {
      return { count: state.count + 1 }
    }
    if (action.type === 'DECREMENT') {
      return { count: state.count - 1 }
    }
    return state
  }

  // 2. 使用reducer函数生成store实例
  const store = Redux.createStore(reducer)

  // 3. 通过store实例的subscribe订阅数据变化
  // 回调函数可以在每次state发生变化的时候自动执行
  store.subscribe(() => {
    console.log('state变化了', store.getState())
    document.getElementById('count').innerText = store.getState().count
  })

  // 4. 通过store实例的dispatch函数提交action更改状态 
  const inBtn = document.getElementById('increment')
  inBtn.addEventListener('click', () => {
    // 增
    store.dispatch({
      type: 'INCREMENT'
    })
  })

  const dBtn = document.getElementById('decrement')
  dBtn.addEventListener('click', () => {
    // 减
    store.dispatch({
      type: 'DECREMENT'
    })
  })

  // 5. 通过store实例的getState方法获取最新状态更新到视图中

</script>

14.1.3 Redux管理数据流程梳理

为了职责清晰,数据流向明确,Redux把整个数据修改的流程分成了三个核心概念,分别是:state、action和reducer

  1. state - 一个对象存放着我们管理的数据状态。
  2. action - 一个对象用来描述你想怎么改数据。
  3. reducer - 一个函数 根据action的描述生成一个新的state。

14.2 Redux与React

14.2.1 环境准备

1)配套工具

在React中使用redux,官方要求安装俩个其他插件 - Redux Toolkit 和 react-redux

  1. Redux Toolkit(RTK)- 官方推荐编写Redux逻辑的方式,是一套工具的集合集,简化书写方式。
  1. react-redux - 用来 链接 Redux 和 React组件 的中间件。

2)配置基础环境

  • 使用 CRA 快速创建 React 项目
npx create-react-app react-redux-pro

大战bug-「npx create-react-app 项目极慢」

更新源:npm config set registry https://registry.npmmirror.com

# 查看修改的结果

npm config get registry

  • cd到项目目录中,安装依赖:

安装配套工具

npm i @reduxjs/toolkit  react-redux
  • 启动项目
npm run start

大战bug-「npm run start 启动失败」

npm install ajv@latest ajv-keywords@latest

成功启动项目:

3)store目录结构设计

  1. 通常集中状态管理的部分都会单独创建一个单独的 store 目录。
  2. 应用通常会有很多个子store模块,所以创建一个 modules 目录,在内部编写业务分类的子store。
  3. store中的入口文件 index.js 的作用是组合modules中所有的子模块,并导出store。

14.2.2 Redux与React - 实现counter

1)整体路径

2)步骤

  • 1)使用React Toolkit 创建 counterStore

默认导出和命名导出

在 JavaScript 的模块系统中,导出(export)和导入(import)允许你将代码分割成不同的模块,然后在需要的地方重用它们。模块可以有命名导出(named exports)和默认导出(default export)。

  • 默认导出(Default Export)

默认导出是模块的主要内容或最常用的部分。一个模块只能有一个默认导出。默认导出可以用任何有效的标识符命名,当导入时,你可以使用任何名称来引用它。

示例:默认导出

// module.js
const someFunction = () => {
  console.log('This is a function from a module.');
};

export default someFunction;

导入默认导出

>// anotherFile.js
import myFunction from './module.js';
myFunction(); // This is a function from a module.
  • 命名导出(Named Exports)

命名导出允许模块导出多个值(函数、对象或变量),每个值都有一个名称。一个模块可以有多个命名导出。

示例:命名导出

>// module.js
export const someFunction = () => {
  console.log('This is a function from a module.');
};

export const anotherFunction = () => {
  console.log('This is another function from the same module.');
};

export const someValue = 'This is a value from a module.';

导入命名导出

>// anotherFile.js
import { someFunction, anotherFunction, someValue } from './module.js';

someFunction(); // This is a function from a module.
anotherFunction(); // This is another function from the same module.
console.log(someValue); // This is a value from a module.
  • 2)为React注入store

react-redux负责把Redux和React 链接 起来,内置 Provider组件 通过 store 参数把创建好的store实例注入到应用中 ,链接正式建立。

  • 3)React组件使用store中的数据

在React组件中使用store中的数据,需要用到一个 钩子函数 - useSelector,它的作用是把store中的数据映射到组件
中,使用样例如下:

useSelector 是一个 React Hook,用于在 React 组件中读取 Redux store 中的状态。它依赖于已经创建好的 store 来获取状态数据。在组件中使用 useSelector 时,它会订阅 store 的变化,并在状态更新时重新计算选择器函数的结果,从而触发组件的更新。

const {count} = useSelector(store=>store.counter)

这段代码使用了 Redux Toolkit 中的 useSelector Hook,其作用是从 Redux store 中提取 counter 部分的状态,并将其解构以获取 count 属性的值。

  • useSelector 是一个 React Hook,它允许你从 Redux store 的状态树中读取数据。
  • store => store.counter 是传递给 useSelector 的选择器函数(selector function)。这个函数接收整个 Redux store 的状态树作为参数,然后返回你想要的部分。在这个例子中,它返回了 counter 这个子状态。
  • const { count } = ... 是 JavaScript 的解构赋值语法。它从 useSelector 返回的对象中提取 count 属性,并将其赋值给一个名为 count 的常量。
  • 4)React组件修改store中的数据

React组件中修改store中的数据需要借助另外一个hook函数 - useDispatch,它的作用是生成提交action对象的 dispatch函数,使用样例如下:

解惑-「为什么需要()=>,不能直接dispatch(decrement())这样调用吗?」

使用箭头函数的原因

  • 延迟执行:通过使用箭头函数 () => dispatch(decrement()),我们创建了一个新的函数实例。这个函数在组件渲染时不会被调用,而是在事件发生时才被调用。这样可以确保 dispatch(decrement()) 只在用户点击按钮时执行。如果直接使用 dispatch(decrement()),那么这个表达式会在组件渲染时立即执行,而不是在事件发生时执行。这是因为 React 会将组件的 JSX 转换为 JavaScript 对象,在这个过程中,所有非函数引用的表达式都会被求值。
  • 控制执行时机:箭头函数提供了一种简单的方式来延迟函数的执行,直到事件实际发生。这种方式在 React 中非常常见,因为它是控制事件处理器执行时机的标准做法。

回顾

  1. 组件中使用哪个hook函数获取store中的数据?useSelector。
  2. 组件中使用哪个hook函数获取dispatch方法?useDispatch。
  3. 如何得到要提交action对象?执行store模块中导出的actionCreater方法。

14.3 提交action传参

14.3.1 需求说明

组件中有俩个按钮 add to 10add to 20 可以直接把count值修改到对应的数字,目标count值是在组件中传递过去的,需要在提交action的时候传递参数。

14.3.2 提交action传参实现需求

在reducers的同步修改方法中添加action对象参数,在调用actionCreater的时候传递参数,参数会被传递到action对象payload属性上。

14.3 异步状态操作

14.3.1 需求理解

14.3.2 异步操作样板代码

  1. 创建store的写法保持不变,配置好同步修改状态的方法

  2. 单独封装一个函数,在函数内部return一个新函数,在新函数中

    2.1 封装异步请求获取数据

    2.2 调用同步actionCreater传入异步数据生成一个action对象,并使用dispatch提交

  3. 组件中dispatch的写法保持不变

解惑-「既然thunk action creator函数里面已经调用了dispatch,为什么这里还需要调用一次dispatch(fetchChannelList());」

在使用 Redux Thunk 时,dispatch(fetchChannelList()) 这行代码的作用是启动整个异步流程。虽然 fetchChannelList 这个 thunk action creator 函数内部确实会调用 dispatch,但这里的 dispatch(fetchChannelList()) 是必要的,原因如下:

启动异步流程

  • 外部触发dispatch(fetchChannelList()) 是从组件或其他地方显式调用的,用来启动异步操作。它告诉 Redux store:“我有一个异步操作需要执行,请处理它。”
  • 中间件介入:当 dispatch(fetchChannelList()) 被调用时,Redux Thunk 中间件会拦截到这个操作。它识别出 fetchChannelList() 返回的是一个函数(thunk 函数),而不是一个普通的 action 对象。然后,thunk middleware 调用这个函数,并传入 dispatchgetState
  • 内部逻辑:在 fetchChannelList 返回的 thunk 函数内部,异步操作(如 API 请求)被执行。在异步操作完成后,内部的 dispatch 调用用来分发一个或多个 action,以更新 Redux store 的状态。

为什么需要外部的 dispatch

  • 符合 Redux 的设计:Redux 的设计要求所有的状态更新都通过 dispatch action 来完成。即使是异步操作,也需要通过 dispatch 来触发和管理。
  • 中间件的作用:Redux Thunk 中间件是设计来处理这种模式的。它等待外部的 dispatch 调用,然后处理返回的函数(thunk)。如果没有外部的 dispatch,thunk middleware 就无法知道何时启动异步逻辑。
  • 组件的控制:在组件中调用 dispatch(fetchChannelList()) 允许你控制何时启动异步操作。例如,你可能希望在组件加载时、用户点击按钮时,或者在特定条件满足时才去获取数据。

总结

  • **外部 dispatch**:启动异步流程,让 Redux Thunk 中间件介入并处理 thunk 函数。
  • **内部 dispatch**:在异步操作完成后,用于分发 action 更新 Redux store 的状态。

这种设计模式使得异步操作的处理更加清晰和可控,同时保持了 Redux 状态管理的一致性和可预测性。

14.3.3 Redux调试 - devtools

安装chrome调试工具:

Redux官方提供了针对于Redux的调试工具,支持实时state信息展示,action提交信息查看等。

14.4 美团外卖案例

14.4.1 案例演示和环境准备

1)案例演示

基本开发思路:使用 RTK(Redux Toolkit)来管理应用状态, 组件负责 数据渲染 和 dispatch action。

2)准备并熟悉环境

克隆项目到本地(内置了基础静态组件和模版)

git clone http://git.itcast.cn/heimaqianduan/redux-meituan.git

安装所有依赖

npm i

启动mock服务(内置了json-server)

npm run serve

启动前端服务

npm run start

十五、ReactRouter

15.1 什么是前端路由?

一个路径 path 对应一个组件 component 当我们在浏览器中访问一个 path 的时候,path 对应的组件会在页面中进行渲染。

15.2 创建路由开发环境

使用路由我们还是采用CRA创建项目的方式进行基础环境配置
1. 创建项目并安装所有依赖
npx create-react-app react-router-pro 
npm i
2. 安装最新的 ReactRouter包 
npm i react-router-dom
3. 启动项目 
npm run start

15.3 快速开始

15.4 结构优化

实际开发中的router配置:

  • src新增一个模块page,page下新建Login和Article两个文件夹(对应两个组件),然后分别在下面创建index.js

  • src新增一个模块router,新建index.js,导入上面的两个组件。

  • 在router下的index.js中配置路由。

  • 项目入口index.js注入router。

14.5 路由导航

什么是路由导航:路由系统中的多个路由之间需要进行路由跳转,并且在跳转的同时有可能需要传递参数进行通信。

14.5.1 声明式导航

声明式导航是指通过在模版中通过 <Link/> 组件描述出要跳转到哪里去,比如后台管理系统的左侧菜单通常使用这种方式进行:

语法说明:通过给组件的to属性指定要跳转到路由path,组件会被渲染为浏览器支持的a链接,如果需要传参直接通过 字符串拼接的方式拼接参数即可。

14.5.2 编程式导航

编程式导航是指通过 useNavigate 钩子得到导航方法,然后通过调用方法以命令式的形式进行路由跳转,比如想在 登录请求完毕之后跳转就可以选择这种方式,更加灵活:

语法说明:通过调用navigate方法传入地址path实现跳转。

14.5.3 导航传参

路由导航传参:

15.6 嵌套路由配置

什么是嵌套路由:在一级路由中又内嵌了其他路由,这种关系就叫做嵌套路由,嵌套至一级路由内的路由又称作二级路由,例如:

15.6.1 嵌套路由配置实现步骤

  1. 使用 children 属性配置路由嵌套关系
  2. 使用 <Outlet/> 组件配置二级路由渲染位置

15.6.2 默认二级路由

场景和配置方式:当访问的是一级路由时,默认的二级路由组件可以得到渲染,只需要在二级路由的位置去掉path,设置index属性为true。

15.7 404 路由配置

404路由场景:当浏览器输入url的路径在整个路由配置中都找不到对应的 path,为了用户体验,可以使用 404 兜底组件进行
渲染。

实现步骤:

  1. 准备一个NotFound组件
  2. 在路由表数组的末尾,以*号作为路由path配置路由

常用的第三方库

动态控制类型名:classnames

生成唯一的随机数:uuid ,地址:https://github.com/uuidjs/uuid

生成固定格式的时间:dayjs ,地址:https://day.js.org/zh-CN/

常用库

x.1 json-server

json-server 是一个轻量级的 Node.js 模块,用于快速搭建本地的 RESTful API 服务器。它使用 JSON 文件作为数据源,可以模拟后端服务器的行为,非常适合前端开发人员在后端 API 尚未就绪时进行开发和测试。以下是 json-server 的一些主要作用和功能:

x.1.1 主要作用

  • 快速搭建 API:无需编写后端代码,只需提供一个 JSON 文件,即可快速生成 RESTful 风格的 API。
  • 前后端分离开发:前端开发者可以在不依赖后端的情况下进行开发,提高开发效率。
  • 模拟后端接口:在后端接口尚未完成时,前端可以使用 json-server 模拟数据接口,方便调试和测试。

x.1.2 使用方法

1)安装 json-server

首先,你需要在全局范围内安装 json-server。打开终端,运行以下命令:

npm install -g json-server

解惑-「npm install -g json-server 为什么要加-g参数?」

在使用 npm install 命令时,-g 参数表示全局安装(global installation)。以下是使用 -g 参数的几个原因和好处:

  1. 全局可用
  • 无需项目依赖:全局安装的包不会被添加到项目的 node_modules 目录中,因此不会成为项目的一部分。这意味着你可以在任何项目中使用该工具,而不需要在每个项目中单独安装。
  • 命令行工具:许多 Node.js 工具(如 json-servercreate-react-appeslint 等)是设计为命令行工具使用的。全局安装后,它们的命令可以在系统的任何地方通过命令行直接调用,而不需要在特定项目的上下文中。
  1. 节省空间
  • 避免重复安装:如果在多个项目中都需要使用同一个工具,全局安装可以避免在每个项目的 node_modules 目录中重复安装该工具,从而节省磁盘空间。
  1. 简化管理
  • 统一更新:全局安装的包可以通过一个命令进行更新,例如 npm update -g,这可以简化对全局工具的管理,确保所有项目都能使用到最新版本的工具。

大战bug-「no matching version found for @tinyhttp/logger@^2.0.0」

分析是因为最新版本的依赖问题,安装特定的旧版本:

npm install -g json-server@0.17.0

2)创建 JSON 数据文件

在你的项目目录中创建一个 JSON 文件,例如 db.json。这个文件将作为你的数据库,存储模拟数据。以下是一个示例:

{
  "posts": [
    { "id": 1, "title": "json-server", "author": "typicode" }
  ],
  "comments": [
    { "id": 1, "body": "some comment", "postId": 1 }
  ],
  "profile": { "name": "typicode" }
}

3)启动 json-server

在终端中,导航到包含 db.json 文件的目录,然后运行以下命令来启动 json-server

json-server --watch db.json

这将启动一个本地服务器,默认监听在 http://localhost:3000。你可以通过访问不同的 URL 来获取数据:

  • http://localhost:3000/posts:获取所有文章
  • http://localhost:3000/comments:获取所有评论
  • http://localhost:3000/profile:获取用户资料

4)使用 json-server 的 RESTful API

json-server 提供了完整的 RESTful API,你可以使用以下方法:

  • GET:获取数据
    • GET /posts:获取所有文章
    • GET /posts/1:获取 ID 为 1 的文章
  • POST:创建数据
    • POST /posts:创建新文章
  • PUT:更新数据
    • PUT /posts/1:更新 ID 为 1 的文章
  • PATCH:部分更新数据
    • PATCH /posts/1:更新 ID 为 1 的文章的部分字段
  • DELETE:删除数据
    • DELETE /posts/1:删除 ID 为 1 的文章

5)配置 json-server

你可以通过添加更多选项来配置 json-server

  • 指定端口

    json-server --watch db.json --port 3001

参考:


文章作者: 司晓凯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 司晓凯 !
  目录