Skip to content

react基础

约 7799 字大约 26 分钟

2025-05-02

1.脚手架创建

1.0 组成部分

  • react 核心语法 react
  • react 操作节点库 react-dom
  • react 跨段原生开发 react navtie

1.1命令

npx create-next-app@latest 文件名

npm create vite@latest 文件名 -- --template react

sacc命令: yarn add -D sass-embedded

项目目录

生命周期

  • 初始化周期 init
  • 挂载周期
    • component
  • 更新周期
  • 卸载周期

2.JSX基础

什么是JSX

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

const message = 'this is message'

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

优势:

  1. HTML的声明式模版写法
  2. JavaScript的可编程能力

1.1JSX高频场景-JS表达式

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

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

    注意

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

const message = 'this is message'

function getAge(){
  return 18
}

function App(){
  return (
    <div>
      <h1>this is title</h1>
      {/* 字符串识别 */}
      {'this is str'}
      {/* 变量识别 */}
      {message}
      {/* 变量识别 */}
      {message}
      {/* 函数调用 渲染为函数的返回值 */}
      {getAge()}
          {/* 样式书写 */}
   <div className="App" style={{color: 'red'}}>
      你好{num}
    </div>
  );
    </div>
  )
}

1.2map渲染

要加上独一无二的key值 React内部框架使用,提升更新新性能


const list = [
  {id:1001, name:'Vue'},
  {id:1002, name: 'React'},
  {id:1003, name: 'Angular'}
]
function App() {
  return (
    <div className="App" style={{color: 'red'}}>
      {/* 你好 */}
      <ul>
        {list.map(item => <li key={item.id}>{item.name}</li>)}
      </ul>
    </div>
  );
}

export default App;

1.3jsx条件渲染

在React中,可以通过逻辑与运算符&&、三元表达式(?😃 实现基础的条件渲染

function App() {
  return (
    <div className="App" style={{color: 'red'}}>
     {/* 逻辑与 && */}
     {true && <span>你好</span>}
     {/* 三元运算 */}
     {false ? <span>true</span> :<span>false</span>}
    </div>
  );
}

export default App;

1.4JSX高频场景-复杂条件渲染

需求:列表中需要根据文章的状态适配 解决方案:自定义函数 + 判断语句


const num = 2

const getImg = () => {
  if(num === 1) return <span>单图</span>
  if(num === 2) return <span>双图</span>
}
function App() {
  return (
    <div className="App" style={{color: 'red'}}>
      {getImg()}
    </div>
  );
}

export default App;

3.react事件绑定

on时间 =》 onClick=


const handelClick = () => {
  console.log('点击l')
  
}
function App() {
  return (
    <div className="App" style={{color: 'red'}}>
      <button onClick={handelClick}>点击</button>
    </div>
  );
}

export default App;

获取时间参数e

const handelClick = (e) => {
  console.log('点击l',e)
  
}
function App() {
  return (
    <div className="App" style={{color: 'red'}}>
      <button onClick={handelClick}>点击</button>
    </div>
  );
}

export default App;

获取自定义参数

// 传递自定义参数
const handelClick = (name) => {
  console.log('点击l',name)
  
}

function App() {
  return (
    <div className="App" style={{color: 'red'}}>
      <button onClick={() => handelClick('jack')}>点击</button>
    </div>
  );
}

export default App;

获取自定义参数和时间参数E

const handelClick = (name,e) => {
  console.log('点击l',name,e)
  
}

function App() {
  return (
    <div className="App" style={{color: 'red'}}>
      <button onClick={(e) => handelClick('jack',e)}>点击</button>
    </div>
  );
}

export default App;

4.React组件基础使用

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

4.1组件基础使用

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


const Button = () => {
  return(
    <button>我是按钮</button>
  )
}
function App() {
  return (
    <div className="App" style={{color: 'red'}}>
      <Button />
    </div>
  );
}
export default App;

外部为文件

const Button = () => {
  return (
    <button>我是按钮</button>
  )
}

export default Button
import Button from "./Button"; // 导入
// const Button = () => {
//   return(
//     <button>我是按钮</button>
//   )
// }
function App() {
  return (
    <div className="App" style={{color: 'red'}}>
      <Button />
    </div>
  );
}

export default App;

5.组件状态管理-useState[数据驱动视图]

useState 是一个 React Hook(函数),它允许我们向组件添加一个状态变量, 从而控制影响组件的渲染结果 和普通JS变量不同的是,状态变量一旦发生变化组件的视图UI也会跟着变化(数据驱动视图)

import { useState } from "react";

function App() {
  // 获取状态变量
  const [count, setCount] = useState(0)// 
  const addClick =  () => {
    // 修改值
    setCount(count + 1) // *count只可以用setCount替换*,不可直接修改 count++
  }
  return (
    <div className="App" style={{color: 'red'}}>
      <button onClick={addClick}>加1</button> <br></br>
      {count}
    </div>
  );
}

export default App;

修改复杂对象

import { useState } from "react";

function App() {
  // 获取状态变量
  const [count, setCount] = useState(0)
  const [name, setName] = useState({name: '张三'})
  const addClick =  () => {
    // 修改值
    setCount(count + 1)
    setName({
      ...name,
      name: '李四'
    })
  }
  return (
    <div className="App" style={{color: 'red'}}>
      <button onClick={addClick}>加1</button> <br></br>
      {count}{name.name}
    </div>
  );
}

export default App;

6.组件的基础样式处理

React组件基础的样式控制有俩种方式,行内样式和class类名控制

<div style={{ color:'red'}}>this is div</div>
.foo{
  color: red;
}
import './index.css'

function App(){
  return (
    <div>
      <span className="foo">this is span</span>
    </div>
  )
}

7.lodash(数组排序工具)

安装

yarn add lodash
import _ from 'lodash' // 导入
const defaultList = [
  {
    // 评论id
    rpid: 3,
    // 用户信息
    user: {
      uid: '13258165',
      avatar: '',
      uname: '周杰伦',
    },
    // 评论内容
    content: '哎哟,不错哦',
    // 评论时间
    ctime: '10-18 08:15',
    like: 88,
  },
  {
    rpid: 2,
    user: {
      uid: '36080105',
      avatar: '',
      uname: '许嵩',
    },
    content: '我寻你千百度 日出到迟暮',
    ctime: '11-13 11:29',
    like: 88,
  },
  {
    rpid: 1,
    user: {
      uid: '30009257',
      avatar,
      uname: '黑马前端',
    },
    content: '学前端就来黑马',
    ctime: '10-19 09:00',
    like: 66,
  },
]
// 当前登录用户信息
const user = {
  // 用户id
  uid: '30009257',
  // 用户头像
  avatar,
  // 用户昵称
  uname: '黑马前端',
}
_.orderBy(数组,排序字段,排序规则)
setCommentList(_.orderBy(commentList,'like','desc'))

8.classnames工具化类名控制器

安装

 yarn add classnames
import classNames from 'classname' // 导入

className={`nav-item ${tabType === item.type && 'active'}`} // 原来
className={classNames(`nav-item`,{active: type === item.type})} // 改后

受控和非受控组件

非受控组件 通过useRef 获取dom ,通过dom获取数据

受控 通过useState 做数据绑定 通过onChange事件 useCount (e.targe.vulue)赋值

9.受控表单绑定

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

<input 
value={value}
onChange={(e) => setValue(e.target.value)}
>  </input>

10.React获取DOM【非受控】

const inp = useRef(null)
<input ref={inp} /> // 绑定
    console.log(inp.current)// 获取DOM

11.Dayjs时间格式化

安装

 yarn add dayjs

导入

import dayjs from 'dayjs'
dayjs().format() 
// 默认返回的是 ISO8601 格式字符串 '2020-04-02T08:02:17-05:00'

dayjs('2019-01-25').format('[YYYYescape] YYYY-MM-DDTHH:mm:ssZ[Z]') 
// 'YYYYescape 2019-01-25T00:00:00-02:00Z'

dayjs('2019-01-25').format('DD/MM/YYYY') // '25/01/2019'

12.组件通信

  • 父传子

    基础实现

    **实现步骤 **

    1. 父组件传递数据 - 在子组件标签上绑定属性
    2. 子组件接收数据 - 子组件通过props参数接收数据
    
    const Son = (props) => {
      // props接收
      return(
        <div>我是Son {props.name}</div>
      )
    }
    const App = () => {
      const name = '名字'
      return(
        // 绑定属性传
        <Son  name={name}/>
      )  
    }
    export default App

    props说明

    props可以传递任意的合法数据,比如数字、字符串、布尔值、数组、对象、函数、JSX image.pngprops是只读对象子组件只能读取props中的数据,不能直接进行修改, 父组件的数据只能由父组件修改

    【插槽】特殊的prop-chilren

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

    image.png
    image.png

React 插槽(Slots)概念及实现方式

在 React 中,"插槽"(Slots)是一种组件设计模式,允许父组件向子组件传递内容或组件,类似于 Vue 中的插槽概念。React 本身没有专门的"插槽"API,但可以通过以下几种方式实现类似功能:

1. 使用 children 属性

传一个 children 就是一个对象

传多个,children 就是一个数组

这是 React 中最基本的插槽实现方式

// 子组件
function Card({ children }) {
  return <div className="card">{children}</div>;
}

// 父组件
function App() {
  return (
    <Card>
      <h1>标题</h1>
      <p>内容</p>
    </Card>
  );
}

React 插槽(Slots)概念及实现方式

在 React 中,"插槽"(Slots)是一种组件设计模式,允许父组件向子组件传递内容或组件,类似于 Vue 中的插槽概念。React 本身没有专门的"插槽"API,但可以通过以下几种方式实现类似功能:

1. 使用 children 属性

这是 React 中最基本的插槽实现方式:

jsx

复制

// 子组件
function Card({ children }) {
  return <div className="card">{children}</div>;
}

// 父组件
function App() {
  return (
    <Card>
      <h1>标题</h1>
      <p>内容</p>
    </Card>
  );
}

2. 多插槽模式(使用 props)

当需要多个插槽位置时,可以使用 props 传递:

// 子组件
function Layout({ header, content, footer }) {
  return (
    <div className="layout">
      <div className="header">{header}</div>
      <div className="content">{content}</div>
      <div className="footer">{footer}</div>
    </div>
  );
}

// 父组件
function App() {
  return (
    <Layout
      header={<h1>网站标题</h1>}
      content={<p>主要内容区域</p>}
      footer={<small>版权信息</small>}
    />
  );
}

3.作用域插槽

// 子组件 Child.jsx
const Child = ({ renderContent }) => {
  const data = { user: "Alice", age: 25 };
  return <div>{renderContent(data)}</div>;
};

// 父组件 Parent.jsx
const Parent = () => {
  return (
    <Child
      renderContent={({ user, age }) => (
        <p>用户名:{user},年龄:{age}</p>
      )}
    />
  );
};
  • 举例
// 子组件 List.jsx
const List = ({ data, renderItem }) => {
  return (
    <ul>
      {data.map((item) => (
        <li key={item.id}>{renderItem(item)}</li>
      ))}
    </ul>
  );
};

// 父组件 Parent.jsx
const Parent = () => {
  const items = [
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
  ];

  return (
    <List
      data={items}
      renderItem={(item) => <span>{item.name}</span>}
    />
  );
};
  • 子传父亲

主要利用父亲传方法给子组件,子组件使用方法传参


const Son = ({onGetMsg}) => {
  // props接收
  const str = '我是子信息'
  return(
    <button onClick={() => onGetMsg(str)}>传信息</button>
  )
}
const App = () => {
  const getMsg = (msg) => {
    console.log(msg)
    
  }
  return(
    // 绑定属性传
    <Son onGetMsg={getMsg}/>
  )
}
export default App
  • 兄弟组件通信
image.png
image.png

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

  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) => {
    setName(name)
  }
  return (
    <div>
      this is App
      <A onGetAName={getAName} />
      <B name={name} />
    </div>
  )
}

export default App
  • 跨层组件通信

实现步骤:

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

import { createContext, useContext } from "react"

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

export  const MsgContext = createContext()  //暴露

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

function B () {
  // 3. 在底层组件 通过useContext钩子函数使用数据
  const msg = useContext(MsgContext)
  return (
    <div>
      this is B compnent,{msg}
    </div>
  )
}

function App () {
  const msg = 'this is app msg'
  return (
    <div>
      {/* 2. 在顶层组件 通过Provider组件提供数据 */}
      <MsgContext.Provider value={msg}>
        this is App
        <A />
      </MsgContext.Provider>
    </div>
  )
}

export default App

13.React副作用管理-useEffect【监听器】

  • useEffect 挂载完成, 卸载前

    useEffect(() => {
        // 操作  componentDidMount
        
        return () => {
            // 销毁代码
        }
        
    },[])// 空数组 ,进界面只执行一次

基础使用【代替数据监听 代替生命周期】

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

image.png
image.png
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 listJson = await res.json()
       console.log(listJson);
       setList(listJson.data.channels)
     }
     getList()
   }  ,[])
   
  return (
    <div className="App">
    <ul>
      {list.map( item => {
        return <li key={item.id}>{item.name}</li>
      })}
    </ul>
    </div>
  );
  
}

export default App;

useEffect依赖说明

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

依赖项副作用功函数的执行时机
没有依赖项组件初始渲染 + 组件更新时执行
空数组依赖只在初始渲染时执行一次
添加特定依赖项组件初始渲染 + 依赖项变化时执行

注意

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

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

14.自定义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

React Hooks使用规则

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

15.Redux与React - 环境准备

Redux虽然是一个框架无关可以独立运行的插件,但是社区通常还是把它与React绑定在一起使用,以一个计数器案例体验一下Redux + React 的基础使用

1. 配套工具

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

  1. Redux Toolkit(RTK)- 官方推荐编写Redux逻辑的方式,是一套工具的集合集,简化书写方式

  2. react-redux - 用来 链接 Redux 和 React组件 的中间件

image.png
image.png

2. 配置基础环境

  1. 使用 CRA 快速创建 React 项目
npx create-react-app react-redux
  1. 安装配套工具
npm i @reduxjs/toolkit  react-redux
  1. 启动项目
npm run start

3.使用

1.使用React Toolkit 创建 counterStore

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



const counterStore = createSlice({
  name:'count',
  // 初始化数据
  initialState: {
    count: 0
  },

  // 修改数据的同步方法
  reducers: {
    add(state) {
      state.count++
    },
    noAdd(state) {
      state.count--
    }
  }
})

// 解构action对象的函数
const {add, noAdd} = counterStore.actions

// 
const countReducer = counterStore.reducer
// 多处action函数和reducer函数
export{add, noAdd}

export default countReducer
import { configureStore } from "@reduxjs/toolkit";
import countReducer from "./modules/counterStore";


const store = configureStore({
  reducer: {
    count: countReducer
  }
})

export default store

2.为React注入store


import { createRoot } from 'react-dom/client'
import App from './App.jsx'
import { Provider } from 'react-redux'
import store from './store/index.jsx'

createRoot(document.getElementById('root')).render(
  <Provider store={store}>
     <App />
  </Provider>
)

4. React组件使用store中的数据

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

image.png
image.png

5. React组件修改store中的数据

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

import { useDispatch, useSelector } from "react-redux"
import { add, noAdd } from "./store/modules/counterStore"
function App() {
const {count} = useSelector(state => state.count)
const dispatch = useDispatch()
return(
  <div className="app">
    <button onClick={() => dispatch(add())}>+</button>
    <span>{count}</span>
    <button onClick={() => dispatch(noAdd())}>-</button>
  </div>
  
)
}

export default App

6.Redux与React - 提交action传参

获取组件里面提供参数

image.png
image.png
toNnm(state,action) {
      state.count = action.payload // action.payload传过来的参数
    }

7.异步函数

编写一个函数返回一个函数

// 异步函数写入数据
const fetchChannelList = () => {
  return async (dispatch) => { // 使用action对象
    const res = await axios.get('http://geek.itheima.net/v1_0/channels')
    dispatch(setChannelList(res.data.data.channels))
  }
}

16.路由快速上手

一个路径 path 对应一个组件 component 当我们在浏览器中访问一个 path 的时候,path 对应的组件会在页面中进行渲染 ![image.png](D:\百度网盘\1. React入门到实战核心精讲\React 基础 - 配套资料\React 基础 - day04+day05\02-笔记\assets\1.png)

2. 创建路由开发环境

使用vite创建项目

 npm create vite@latest react-router-pro -- --template react
npm i

安装最新的ReactRouter包

npm i react-router-dom

启动项目

npm run dev

3.路由使用

文件目录

  1. 编写路由表
import { lazy } from 'react';
import { createBrowserRouter } from 'react-router-dom'

const Login = lazy(() => import('../page/login/index')) // 懒加载
const Article = lazy(() => import('../page/Article/index'))

const router = createBrowserRouter([ // 获取router实例
  {
    path: '/login',
    element: <Login />
  },
  {
    path: 'article',
    element: <Article />
  }
])

export default router
  1. 导入router实例
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
import { RouterProvider } from 'react-router-dom'
import router from './router/index.jsx'

createRoot(document.getElementById('root')).render(
  <RouterProvider router={router}> // 注入router实例
    <App />
  </RouterProvider>

)

4.路由跳转

4.1. 声明式导航

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

import { Link } from "react-router-dom"

const Login = () => {

 return (

    <div>

   我是登录页

   <Link *to*='/article'>跳转文章页</Link>

  </div>

 )

}

export default Login

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

4.2. 编程式导航

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

import { useNavigate } from "react-router-dom"

const Login = () => {
  const navigate = useNavigate()
  const toArticle = () => {
    navigate('/article')
  }
  return (
    <div>
      我是登录页
      <button onClick={toArticle}>文章页</button>
    </div>
  )
}

export default Login

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

5.导航传参

5.1.useSearchParams传参

  • 跳转书写
 navigate('/article?id=100&name=小赵')
  • 接收
const [params] = useSearchParams()
  console.log(params.get('id'))
  console.log(params.get('name'))

5.2.Params传参

  • 路由表配置
{
    path: '/article/:id?',
    element: <Article />
  }
  • 跳转传参
navigate('/article/101')
  • 接收
const params1 = useParams()
  console.log(params1.id)

6.二级路由 利用children

  • 路由表的编写 children: []
 {
    path: '/',
    element: <Layout />,
    // 二级路由表书写
    children: [
      {
        path: 'board',
        element: <Board />
      },
      {
        path: 'about',
        element: <About />
      }
    ]
  },
  • 出口编写
const Layout = () => {
  return (
    <div>
      我是Layout
      <Link to='/board'>面板 </Link>
      <Link to='/about'> 关于</Link>
      {/* 二级路由出口 */}
      <Outlet />  // 出口
    </div>

  )
}

7.默认二级路由

将路由表里面的path 去掉 替换为 index: true

  • 修改路由表
children: [
      {
        index: true, // 默认二级路由
        element: <Board />
      },
      {
        path: '/about',
        element: <About />
      }
    ]

8. 404路由配置 路径找不到

*通配符

{
    path: '*', //匹配所有路径, 必须写最后
    element: <NotFound />
}

9. 俩种路由模式

各个主流框架的路由常用的路由模式有俩种,history模式和hash模式, ReactRouter分别由 createBrowerRouter 和 createHashRouter 函数负责创建

路由模式url表现底层原理是否需要后端支持
historyurl/loginhistory对象 + pushState事件需要
hashurl/#/login监听hashChange事件不需要

17.移动库antd-mobile

命令

npm i antd-mobile

antD主题定制

1. 定制方案

2. 实现方式

  1. 全局定制

  2. 局部定制

18.配置别名路径@

1 安装 @types/node

npm i -D @types/node

2 在 vite.config.ts 中添加配置:@

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';

export default defineConfig({
  plugins: [
    react(),
  ],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src')
    },
  }
});

3 配置路径别名的提示

虽然现在路径别名已经有了,但是在文件中输入@是没有提示路径的。

需要我们在 tsconfig.json 或 jsconfig.json 中,添加配置项:

如果在使用 vite 创建项目时,选择的是 TypeScript 项目,会自动生成 tsconfig.json 文件; 若选择的是 JavaScript 项目,可能不会自动生成 jsconfig.json 文件,那么我们手动创建一个即可。

jsconfig.json 文件配置:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      // 解决项目中使用@作为路径别名,导致vscode无法跳转文件的问题
      "@/*": ["src/*"]
    },
    // 解决prettier对于装饰器语法的警告
    "experimentalDecorators": true,
    // 解决.jsx文件无法快速跳转的问题
    "jsx": "preserve"
  },
}

19.数据Mock实现

在前后端分类的开发模式下,前端可以在没有实际后端接口的支持下先进行接口数据的模拟,进行正常的业务功能开发

1. 常见的Mock方式

2. json-server实现Mock

实现步骤:

  1. 项目中安装json-server npm i -D json-server
  2. 准备一个json文件 (素材里获取)
  3. 添加启动命令
image-20250225201814696
image-20250225201814696
  1. 访问接口进行测试

20.组件库antd使用

命令

npm install antd --save

官网地址:在 Vite 中使用 - Ant Design

21.使用gitee管理项目

在官网创建一个仓库

然后在控制台输入:git init

上传项目:git add .

git commit -m "干了什么"

git push

git push --set-upstream origin master

22 全局样式初始化

Normalize.css: Make browsers render all elements more consistently.

主文件导入 import 'normalize.css'

23 echarts 图标的使用

安装echarts

npm i echarts

实现基础Demo

import * as echarts from 'echarts'

const Home = () => {
  const chartRef = useRef(null)
  useEffect(() => {
    // 1. 生成实例
    const myChart = echarts.init(chartRef.current)
    // 2. 准备图表参数
    const option = {
      xAxis: {
        type: 'category',
        data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
      },
      yAxis: {
        type: 'value'
      },
      series: [
        {
          data: [120, 200, 150, 80, 70, 110, 130],
          type: 'bar'
        }
      ]
    }
    // 3. 渲染参数
    myChart.setOption(option)
  }, [])

24 准备富文本编辑器

https://www.wangeditor.co

实现步骤

  1. 安装富文本编辑器
  2. 导入富文本编辑器组件以及样式文件
  3. 渲染富文本编辑器组件
  4. 调整富文本编辑器的样式

代码落地 1-安装 react-quill

npm i react-quill@2.0.0-beta.2

2-导入资源渲染组件

25 useReducer基础使用

作用: 让 React 管理多个相对关联的状态数据

import { useReducer } from 'react'

// 1. 定义reducer函数,根据不同的action返回不同的新状态
function reducer(state, action) {
  switch (action.type) {
    case 'INC':
      return state + 1
    case 'DEC':
      return state - 1
    default:
      return state
  }
}

function App() {
  // 2. 使用useReducer分派action
  const [state, dispatch] = useReducer(reducer, 0)
  return (
    <>
      {/* 3. 调用dispatch函数传入action对象 触发reducer函数,分派action操作,使用新状态更新视图 */}
      <button onClick={() => dispatch({ type: 'DEC' })}>-</button>
      {state}
      <button onClick={() => dispatch({ type: 'INC' })}>+</button>
    </>
  )
}

export default App

更新流程

image.png
image.png

分派action传参

做法:分派action时如果想要传递参数,需要在action对象中添加一个payload参数,放置状态参数

// 定义reducer

import { useReducer } from 'react'

// 1. 根据不同的action返回不同的新状态
function reducer(state, action) {
  console.log('reducer执行了')
  switch (action.type) {
    case 'INC':
      return state + 1
    case 'DEC':
      return state - 1
    case 'UPDATE':
      return state + action.payload
    default:
      return state
  }
}

function App() {
  // 2. 使用useReducer分派action
  const [state, dispatch] = useReducer(reducer, 0)
  return (
    <>
      {/* 3. 调用dispatch函数传入action对象 触发reducer函数,分派action操作,使用新状态更新视图 */}
      <button onClick={() => dispatch({ type: 'DEC' })}>-</button>
      {state}
      <button onClick={() => dispatch({ type: 'INC' })}>+</button>
      <button onClick={() => dispatch({ type: 'UPDATE', payload: 100 })}>
        update to 100
      </button>
    </>
  )
}

export default App

25. useMemo 消耗非常大的计算

作用:它在每次重新渲染的时候能够缓存计算的结果

看个场景

下面我们的本来的用意是想基于count的变化计算斐波那契数列之和,但是当我们修改num状态的时候,斐波那契求和函数也会被执行,显然是一种浪费

// useMemo
// 作用:在组件渲染时缓存计算的结果

import { useState } from 'react'

function factorialOf(n) {
  console.log('斐波那契函数执行了')
  return n <= 0 ? 1 : n * factorialOf(n - 1)
}

function App() {
  const [count, setCount] = useState(0)
  // 计算斐波那契之和
  const sumByCount = factorialOf(count)

  const [num, setNum] = useState(0)

  return (
    <>
      {sum}
      <button onClick={() => setCount(count + 1)}>+count:{count}</button>
      <button onClick={() => setNum(num + 1)}>+num:{num}</button>
    </>
  )
}

export default App

useMemo缓存计算结果

思路: 只有count发生变化时才重新进行计算

import { useMemo, useState } from 'react'

function fib (n) {
  console.log('计算函数执行了')
  if (n < 3) return 1
  return fib(n - 2) + fib(n - 1)
}

function App() {
  const [count, setCount] = useState(0)
  // 计算斐波那契之和
  // const sum = fib(count)
  // 通过useMemo缓存计算结果,只有count发生变化时才重新计算
  const sum = useMemo(() => {
    return fib(count)
  }, [count])

  const [num, setNum] = useState(0)

  return (
    <>
      {sum}
      <button onClick={() => setCount(count + 1)}>+count:{count}</button>
      <button onClick={() => setNum(num + 1)}>+num:{num}</button>
    </>
  )
}

export default App

React.memouseMemo

作用:它在每次重新渲染的时候能够缓存计算的结果

看个场景

下面我们的本来的用意是想基于count的变化计算斐波那契数列之和,但是当我们修改num状态的时候,斐波那契求和函数也会被执行,显然是一种浪费

// useMemo
// 作用:在组件渲染时缓存计算的结果

import { useState } from 'react'

function factorialOf(n) {
  console.log('斐波那契函数执行了')
  return n <= 0 ? 1 : n * factorialOf(n - 1)
}

function App() {
  const [count, setCount] = useState(0)
  // 计算斐波那契之和
  const sumByCount = factorialOf(count)

  const [num, setNum] = useState(0)

  return (
    <>
      {sum}
      <button onClick={() => setCount(count + 1)}>+count:{count}</button>
      <button onClick={() => setNum(num + 1)}>+num:{num}</button>
    </>
  )
}

export default App

useMemo缓存计算结果

思路: 只有count发生变化时才重新进行计算

import { useMemo, useState } from 'react'

function fib (n) {
  console.log('计算函数执行了')
  if (n < 3) return 1
  return fib(n - 2) + fib(n - 1)
}

function App() {
  const [count, setCount] = useState(0)
  // 计算斐波那契之和
  // const sum = fib(count)
  // 通过useMemo缓存计算结果,只有count发生变化时才重新计算
  const sum = useMemo(() => {
    return fib(count)
  }, [count])

  const [num, setNum] = useState(0)

  return (
    <>
      {sum}
      <button onClick={() => setCount(count + 1)}>+count:{count}</button>
      <button onClick={() => setNum(num + 1)}>+num:{num}</button>
    </>
  )
}

export default App

27. React.memo 子组件在Props没变,不会渲染 引用数据类型会渲染,没效果 可以结合 useMemo缓存 完成不渲染

作用:允许组件在props没有改变的情况下跳过重新渲染

组件默认的渲染机制

默认机制:顶层组件发生重新渲染,这个组件树的子级组件都会被重新渲染

// memo
// 作用:允许组件在props没有改变的情况下跳过重新渲染

import { useState } from 'react'

function Son() {
  console.log('子组件被重新渲染了')
  return <div>this is son</div>
}

function App() {
  const [, forceUpdate] = useState()
  console.log('父组件重新渲染了')
  return (
    <>
      <Son />
      <button onClick={() => forceUpdate(Math.random())}>update</button>
    </>
  )
}

export default App

使用React.memo优化

机制:只有props发生变化时才重新渲染 下面的子组件通过 memo 进行包裹之后,返回一个新的组件MemoSon, 只有传给MemoSon的props参数发生变化时才会重新渲染

import React, { useState } from 'react'

const MemoSon = React.memo(function Son() {
  console.log('子组件被重新渲染了')
  return <div>this is span</div>
})

function App() {
  const [, forceUpdate] = useState()
  console.log('父组件重新渲染了')
  return (
    <>
      <MemoSon />
      <button onClick={() => forceUpdate(Math.random())}>update</button>
    </>
  )
}

export default App

props变化重新渲染

import React, { useState } from 'react'

const MemoSon = React.memo(function Son() {
  console.log('子组件被重新渲染了')
  return <div>this is span</div>
})

function App() {
  console.log('父组件重新渲染了')

  const [count, setCount] = useState(0)
  return (
    <>
      <MemoSon count={count} />
      <button onClick={() => setCount(count + 1)}>+{count}</button>
    </>
  )
}

export default App

props的比较机制

对于props的比较,进行的是‘浅比较’,底层使用 Object.is 进行比较,针对于对象数据类型,只会对比俩次的引用是否相等,如果不相等就会重新渲染,React并不关心对象中的具体属性

import React, { useState } from 'react'

const MemoSon = React.memo(function Son() {
  console.log('子组件被重新渲染了')
  return <div>this is span</div>
})

function App() {
  // const [count, setCount] = useState(0)
  const [list, setList] = useState([1, 2, 3])
  return (
    <>
      <MemoSon list={list} />
      <button onClick={() => setList([1, 2, 3])}>
        {JSON.stringify(list)}
      </button>
    </>
  )
}

export default App

说明:虽然俩次的list状态都是 [1,2,3] , 但是因为组件App俩次渲染生成了不同的对象引用list,所以传给MemoSon组件的props视为不同,子组件就会发生重新渲染

27. useCallback 父传子是函数 引用数据 缓存函数 函数不变 不会导致子组件渲染

看个场景

上一小节我们说到,当给子组件传递一个引用类型prop的时候,即使我们使用了memo 函数依旧无法阻止子组件的渲染,其实传递prop的时候,往往传递一个回调函数更为常见,比如实现子传父,此时如果想要避免子组件渲染,可以使用 useCallback缓存回调函数

useCallback缓存函数

useCallback缓存之后的函数可以在组件渲染时保持引用稳定,也就是返回同一个引用

// useCallBack

import { memo, useCallback, useState } from 'react'

const MemoSon = memo(function Son() {
  console.log('Son组件渲染了')
  return <div>this is son</div>
})

function App() {
  const [, forceUpate] = useState()
  console.log('父组件重新渲染了')
  const onGetSonMessage = useCallback((message) => {
    console.log(message)
  }, [])

  return (
    <div>
      <MemoSon onGetSonMessage={onGetSonMessage} />
      <button onClick={() => forceUpate(Math.random())}>update</button>
    </div>
  )
}

export default App

28. forwardRef 透传 父组件获取子组件的DOM

作用:允许组件使用ref将一个DOM节点暴露给父组件

import { forwardRef, useRef } from 'react'

const MyInput = forwardRef(function Input(props, ref) {
  return <input {...props} type="text" ref={ref} />
}, [])

function App() {
  const ref = useRef(null)

  const focusHandle = () => {
    console.log(ref.current.focus())
  }

  return (
    <div>
      <MyInput ref={ref} />
      <button onClick={focusHandle}>focus</button>
    </div>
  )
}

export default App

29 useImperativeHandle 父组件 获取子组件的方法

作用:如果我们并不想暴露子组件中的DOM而是想暴露子组件内部的方法

import { forwardRef, useImperativeHandle, useRef } from 'react'

const MyInput = forwardRef(function Input(props, ref) {
  // 实现内部的聚焦逻辑
  const inputRef = useRef(null)
  const focus = () => inputRef.current.focus()

  // 暴露子组件内部的聚焦方法
  useImperativeHandle(ref, () => {
    return {
      focus,
    }
  })

  return <input {...props} ref={inputRef} type="text" />
})

function App() {
  const ref = useRef(null)

  const focusHandle = () => ref.current.focus()

  return (
    <div>
      <MyInput ref={ref} />
      <button onClick={focusHandle}>focus</button>
    </div>
  )
}

export default App

30. ZUSTAND 公共状态数据的使用

效果和数据Redux一样

官网: ZUSTAND 中文文档 | ZUSTAND

import { useEffect } from 'react'
import { create } from 'zustand'

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

const store = create((set) => {
  // 需要返回一个对象
  return {
    num: 1,  //初始变量
    add: () => set((state) => ({ num: state.num + 1 })),  // set是一个固定方法 修改变量的值的方法  需要返回一个对象 
    // 如果需要用到原有的值  须在里面再写一个函数  用state获取


    // 异步方法
    channelList: [],
    fetchChannelList: async () => {
      const res = await fetch(URL)
      const jsonData = await res.json()
      set({ channelList: jsonData.data.channels })
    }
  }
})
function App() {

  // const num = store(state => state.num)
  const { num, add, fetchChannelList, channelList } = store()

  // 获取数据

  useEffect(() => {
    fetchChannelList()
  }, [fetchChannelList])
  useEffect(() => {
    const str = channelList.map(item => (
      <div key={item.id}>{item.name}</div>
    ))

    console.log(str)

  })

  // console.log(channelList)

  return (
    <>
      {num}
      <button onClick={add}>加一</button>

      {channelList.map(item => (
        <div key={item.id}>{item.name}</div>
      ))}
    </>
  )
}

export default App

切片模式

场景:当我们单个store比较大的时候,可以采用一种切片模式进行模块拆分再组合

拆分并组合切片

import { create } from 'zustand'

// 创建counter相关切片
const createCounterStore = (set) => {
  return {
    count: 0,
    setCount: () => {
      set(state => ({ count: state.count + 1 }))
    }
  }
}

// 创建channel相关切片
const createChannelStore = (set) => {
  return {
    channelList: [],
    fetchGetList: async () => {
      const res = await fetch(URL)
      const jsonData = await res.json()
      set({ channelList: jsonData.data.channels })
    }
  }
}

// 组合切片
const useStore = create((...a) => ({
  ...createCounterStore(...a),
  ...createChannelStore(...a)
}))

组件使用

function App() {
  const {count, inc, channelList, fetchChannelList } = useStore()
  return (
    <>
      <button onClick={inc}>{count}</button>
      <ul>
        {channelList.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </>
  )
}

export default App

对接DevTools

简单的调试我们可以安装一个 名称为 simple-zustand-devtools 的调试工具

安装调试包

npm i simple-zustand-devtools -D

配置调试工具

import create from 'zustand'

// 导入核心方法
import { mountStoreDevtool } from 'simple-zustand-devtools'

// 省略部分代码...


// 开发环境开启调试
if (process.env.NODE_ENV === 'development') {
  mountStoreDevtool('channelStore', useChannelStore)
}


export default useChannelStore

打开 React调试工具