React 学习笔记

https://zh-hans.react.dev/learn

From 黑马B站的视频(React 18.2.0 )

1.简介

React由Meta公司研发,是一个用于构建Web和原生交互界面的库

较于其它前端框架 优势: 丰富的生态,跨平台支持

2.搭建开发环境

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

如果提示:输入y 其它情况失败自行排查 比如node版本等
Need to install the following packages:
create-react-app@5.0.1
Ok to proceed? (y) y

我这里报错 失败【可能是我个人电脑问题 】我各种尝试npm镜像之类还是失败了

第二天的时候 尝试了npm install -g react  , npm install -g react-dom,  npm install -g react-scripts 再去create-react-app  first-demo  其实npm install -g react-scripts仍然报错network connectivity的错误。但总算成功了。

进入项目文件夹 npm start 启动项目 。但是我又localhost refused to connect. 用解决了

localhost:3000 拒绝访问解决办法-CSDN博客

index.js文件介绍 -- 项目入口 从这里开始运行

App.js -- 项目根组件

3.JSX

JSX是JavaScript和XMl(HTML)的缩写,表示在JS代码中编写HTML模版结构,它是React中构建UI的方式: 既有HTML的声明式模版,也有JS的可编程能力

3.1JSX的本质

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

Babel · The compiler for next generation JavaScript

3.2 jsx中使用js表达式

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

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

3.3 JSX实现列表渲染

使用原生js种的map方法 实现列表渲染。 给独一无二的key(字符串或者number)解决报错问题——react内部使用提升更新性能

3.4 JSX实现条件渲染

3.4.1 逻辑与运算符 &&

3.4.2 三元表达式 ? :

3.4.3 复杂条件渲染

自定义函数 + 判断语句

4.React的事件绑定

on + 事件名称 = { 事件处理程序 },整体上遵循驼峰命名法

使用事件参数:事件回调函数中设置形参e即可

传递自定义参数:事件绑定的位置改造成箭头函数的写法,在执行clickHandler实际处理业务函数的时候传递实参  (注意:不能直接写函数调用)

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

5.组件使用

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

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

5.1 基础样式处理

5.1.1 行内样式(不推荐)

const fontStyle = {
  fontStyle: 'italic'
}
function App() {
  return (
    <div className="App">
        // 行内样式多单词要采用驼峰命名, 样式对象可以写成变量的形式
      <span style={{...fontStyle,color:'red',fontSize:'22px'}}>is red</span>
    </div>
  );
}

5.1.2 class类名

className ='xxx'

// index.css
.foo {
    color: blue;
}

// 使用
import './index.css'
function App() {
  return (
    <div className="App">
      <span className ='foo'>class foo</span>
    </div>
  );
}

tab页点击高亮展示样式

const [activeTab , setactiveTab] =useState('hot')
const tabClick = (type)=>{
    setactiveTab(type)
  }

{tabs.map(item=>  <span key={item.type} className={`nav-item ${activeTab === item.type && 'active'}`} onClick={()=> tabClick(item.type)}>{item.text}</span>)}

5.1.3 classnames优化类名控制

npm install classnames 然后对应的组件 import classnames from 'classnames' 就可以使用了

classnames - npm

// 静态类名直接写'xxx' 类名之间, 动态的用{ xxx : 条件}
className = {classnames('nav-item', {active: activeTab === item.type})}

5.2 组件状态管理-useState

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

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

import { useState } from "react";
function App() {
  //调用useState添加一个状态变量  count-状态变量 setCount-修改状态变量的方法
  const [ count, setCount ] = useState(0)
  const [ form, setForm ] = useState({
    name: 'jack'
  })
  // 点击事件回调
  const handleCkick = ()=>{  
    // 不能直接count++ 
    // 用传入的新值修改count 用新的count 渲染UI
    setCount(count + 1)
  }
  const changeForm = ()=>{  
    setForm({
      ...form,
      name:'irene'
    })
  }
  return (
    <div className="App">
      <button onClick={ handleCkick}>{count}</button>
      <button onClick={ changeForm}>{form.name}</button>
    </div>
  );
}

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

5.2.1 受控表单绑定

const [comment , setcomment] =useState('')

 <textarea
      value={comment} // value绑定state
      onChange={(e)=> setcomment(e.target.value)} // 输入框最新值给state
      className="reply-box-textarea"
      placeholder="发一条友善的评论"
    />

5.2.2 非受控表单绑定

通过获取DOM的方式获取表单的输入数据 使用useRef钩子函数

1.useRef生成ref对象 绑定到dom标签身上
import { useRef} from "react";
const inputRef = useRef(null)
2.dom可用时,ref.current获取dom/渲染完毕之后dom生成之后才可用
  const btnClick = ()=> {
    console.dir(inputRef.current);
  }

<input  type='text' ref={inputRef} />
<button onClick={btnClick}></button>

5.3 组件通信

5.3.1 父传子

// 2、子组件姐搜狐数据 props参数
function  Son (props){
  console.log(props);// 打印可知是对象 里面包含了父组件传递的所有数据

    // 子组件只能读取props中的数据,不能直接进行修改, 父组件的数据只能由父组件修改
  return  <div> {props.name}
            <div> {props.age}</div>
            <div> {props.isTrue && '为真'}</div>
            <div> {props.obj.name}</div>
            {props.cb()}
            {props.child}
          </div>
}

{/* 1、父组件传递数据 在子组件的标签上绑定 xxx={} 可以传递任意的合法数据,比如数字、字符串、布尔值、数组、对象、函数、JSX */}
<Son name={name}
    age={20}
    isTrue={false}
    list={['vue','react']}
    obj={{name: 'ii'}}
    cb ={()=> console.log(123)}
    child = {<span> this is child</span>}
></Son>

特殊的prop  children

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

5.3.2 子传父

在子组件中调用父组件中的函数并传递参数

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

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

export default App

5.3.3 兄弟组件通信

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

  1. A组件先通过子传父的方式把数据传递给父组件

  2. 拿到数据之后通过父传子的方式再传递给B组件

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>
  )
}

5.3.4 跨层组件通信

① 使用 createContext方法创建一个上下文对象Ctx

② 在顶层组件(App)中通过 Ctx.Provider 组件提供数据

③ 在底层组件(B)中通过 useContext 钩子函数获取消费数据

import { createContext, useContext, useState } from "react";

//APP → A → B

// 1. createContext方法创建一个上下文对象
const MsgContext = createContext()

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


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

5.4 副作用管理-useEffect

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

 useEffect(() =>  {} ,[]) // 参数1 是函数(副作用函数)里面写要执行的操作, 参数2 可选参数 数组 里面放依赖项 如果为[]空数组 副作用函数只会在组件渲染完毕之后执行一次

 const [list , setList] =useState([])
 // 渲染完毕去发送请求
 useEffect(()=>{
    // 注意这里写请求方法的方式 上图为练习中错误写法报错
    async function getList (){
      const res = await fetch('http://geek.itheima.net/v1_0/channels')
      const jsonRes = await res.json() // 这里需要await
      setList(jsonRes.data.channels)
    }
    getList()
  },[])

5.4.1依赖项

依赖项副作用功函数的执行时机
没有依赖项组件初始渲染 + 组件更新时执行
空数组依赖只在初始渲染时执行一次
添加特定依赖项组件初始渲染 + 依赖项变化时执行( 如果有多个值的数组,会比较每一个值,有一个变化就执行)

注意当没有依赖项 副作用函数操作又改变数据(setXxx())时 会无限循环

React useEffect 两个参数你用对了吗,这也许是最全的使用说明书-CSDN博客

5.4.2 清除副作用

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

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

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

5.5 自定义Hook实现

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

// 封装hook 布尔切换的逻辑
function useToggle (){
  // 可复用逻辑代码
  const [value,setValue] = useState(true)
  const toggle = () => setValue(!value)
  // 哪些状态和回调需要在其他组件中使用 可以是对象也可以是数组
  return {
    value,
    toggle
  }
}

使用
const {value ,toggle} = useToggle()

{value && <div></div>}
<button onClick={toggle}>toggle</button>

5.6 React Hooks使用规则

  1. 只能在组件中或者其他自定义Hook函数中调用

  2. 只能在组件的顶层调用,不能嵌套在if、for、其它的函数中

抽象组件 

原则:App作为“智能组件”负责数据的获取,Item作为“U组件”负责数据的渲染

6. Redux

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

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

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

1. state - 一个对象 存放着我们管理的数据状态

2. action - 一个对象 用来描述你想怎么改数据

3. reducer - 一个函数 根据action的描述生成一个新的state

 // 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更改状态 
 store.dispatch({
      type: 'INCREMENT'
    })

6.1 项目中使用Redux

安装俩个其他插件 - Redux Toolkit 和 react-redux

  1. Redux Toolkit(RTK)- 官方推荐编写Redux逻辑的方式,是一套工具的集合集,简化书写方式【简化store的配置方式 内置immer支持可变式状态修改 内置thunk更好的异步创建】

  2. react-redux - 用来 链接 Redux 和 React组件 的中间件【获取 更新】

npm i @reduxjs/toolkit  react-redux 

新建store文件夹  里面新建一个i入口文件index.js【组合所有子模块 导出store】  一个modules目录

6.1.1 使用React Toolkit 创建 counterStore

// index.js
import { configureStore } from "@reduxjs/toolkit";
// 导入子模块reducer
import couterReducer from './modules/counterStore'

const store = configureStore({
    reducer:{
        counter: couterReducer
    }
})
export default store

// counterStore.js
import { createSlice } from "@reduxjs/toolkit";
const counterStore = createSlice({
    name:'counter',// 独一无二
    // 初始化state
    initialState:{
        count: 0
    },
    // 修改状态方法  同步 支持直接修改
    reducers:{
        increment(state){
            state.count++
        },
        decrement(state){
            state.count--
        },
    }
})
// 解构出actionCreater函数
const { increment, decrement} = counterStore.actions
// 获取reducer
const couterReducer = counterStore.reducer
// 按需导出actionCreater
export  {increment, decrement}
// 默认导出reducer
export default couterReducer

6.1.2 为React注入store

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

//index.js
//react必要的两个核心包
import React from 'react';
import ReactDOM from 'react-dom/client';
// 导入项目的根组件
import App from './App';
// 导入store
import store from './store'
// 导入store提供组件Provider
import { Provider } from 'react-redux'


// 把App根组件渲染到id为root的dom节点上---在public/index.html
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  // 提供store数据
  <Provider store={store}>
    <App />
  </Provider>
);

6.1.3  组件使用store中的数据

用到一个钩子函数 - useSelector,它的作用是把store中的数据映射到组件中

 const { count } = useSelector( state=> state.counter) // state.xxx 这个xxx是Store入口文件index.js里对应模块的名称

6.1.4  组件修改store中的数据

hook函数 - useDispatch,它的作用是生成提交action对象的dispatch函数

import { useDispatch, useSelector } from "react-redux";
import { decrement, increment } from "./store/modules/counterStore";

function App() {
  const { count} = useSelector( state=> state.counter)
  const dispatch = useDispatch() 
  return (
    <div className="app">
// 执行store模块中导出的actioncreater方法 得到要提交的action对象 然后dispatch
      <button onClick={()=> dispatch(increment())}>+</button>
      {count}
      <button onClick={()=> dispatch(decrement())}>-</button>
    </div>
);
}

6.1.5 提交action传参

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

// couterStore.js
const counterStore = createSlice({
    name:'counter',
    initialState:{
        count: 0
    },
    reducers:{
        increment(state, action){
            state.count += action.payload || 1
        },
    }
})

// 组件中
 <button onClick={()=> dispatch(increment(10))}>+10</button>

6.1.6 异步action处理

与 同步的区别主要是要单独封装一个函数,在函数内部return一个新函数,在新函数中 封装异步请求获取数据、 调用同步actionCreater传入异步数据生成一个action对象,并使用dispatch提交

// 1、channelStore.js
const channelStore = createSlice(省略 reducers里的同步方法 setChannelList 要传action参数)
// 创建异步
const { setChannelList } = channelStore.actions //【解构出actionCreater】
const url = 'http://geek.itheima.net/v1_0/channels'
// 封装一个函数 在函数中return一个新函数 在新函数中封装异步
// 得到数据之后通过dispatch函数 触发修改
const fetchChannelList = () => {
  return async (dispatch) => {
    const res = await axios.get(url)
    dispatch(setChannelList(res.data.data.channels))
  }
}

export { fetchChannelList }

const channelReducer = channelStore.reducer // 【这里是.reducer 不是reducers】
export default channelReducer

2、组件中

import { fetchChannelList } from './store/channelStore'

const { channelList } = useSelector(state => state.channel)

  useEffect(() => {
    dispatch(fetchChannelList())
  }, [dispatch])

7.react router

安装 npm i react-router-dom

新建router文件夹,新增index.js入口 ,其中引入组件 用createBrowserRouter创建router实例并导出

// router/index.js
import Login from '../page/Login'
import Article from '../page/Article'

import { createBrowserRouter } from 'react-router-dom'
const router = createBrowserRouter([
    {
        path: '/login',
        element: <Login />
    },
    {
        path:'/article',
        element: <Article />
    }
 ])  
export default router

//index.js
import { RouterProvider } from 'react-router-dom'
import router from './router'
ReactDOM.createRoot(document.getElementById('root')).render(
  <RouterProvider router={router}/>
)

7.1路由导航

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

7.1.1 声明式导航

通过 <Link/> 组件描述出要跳转到哪里去,to属性指定要跳转到路由path,组件会被渲染为浏览器支持的a链接,如果需要传参直接通过字符串拼接的方式拼接参数即可

import { Link } from "react-router-dom"
<Link to="/article">tiaozhuan</Link>

7.1.2 编程式导航

通过 useNavigate 钩子得到导航方法,然后通过调用方法以命令式的形式进行路由跳转,更加灵活.

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

const navigate = useNavigate()
return <div>我是home
    <button onClick={()=> navigate('/article')}>tiaozhuan</button>
</div>   

7.1.3 路由传参

1、searchParams传参
navigate('/article?id=1001&name=jack')

import { useSearchParams } from "react-router-dom"
const [params] = useSearchParams()
let id = params.get('id')


2、 params传参
navigate('/article/1001/jack')

路由配置参数占位符
path:'/article/:id/:name',

import { useParams } from "react-router-dom"
const params = useParams()
let id = params.id

7.2 嵌套路由

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

// 使用 children属性配置路由嵌套关系  
const router = createBrowserRouter([
    {
        path:'/',
        element: <Layout />,
        children:[
            {
                index:true, // 默认二级路由 去掉path 设置index:true
                element: <Home />
            },
            {
                path:'article',
                element: <Article />
            },
        ]
    }
])

// 
import { Outlet } from "react-router-dom"
// 使用 <Outlet/> 组件配置二级路由渲染位置
<Outlet></Outlet>

7.3 404路由配置

// 准备一个NotFound组件
const NotFound = ()=>{
    return <div>404</div>   
}
export default NotFound

//在路由表数组的末尾,以*号作为路由path配置路由
    {
        path:'*',
        element:<NotFound />
    }

7.4 俩种路由模式

history模式和hash模式, ReactRouter分别由 createBrowerRouter 和 createHashRouter 函数负责创建

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

8. 开发

8.1 其他常用hooks

省略useState useDispath useEffect  useSelector 等

8.1.1 useMemo

缓存用的,只有当一个依赖项改变的时候才会发生变化,

 //  参数1:是一个创建依赖项函数  参数2:这个是个数组,存放着被依赖的项,只有当这个数组里面的变量发生了变化,才会调用参数1的函数
 useMemo(()=>{ 
        // return 计算之后的值
    },[])

useMemo是在useEffect前

useEffect可看成class组件里面的componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合,

而useMemo则可以看作是componentWillUpdate函数。

顺序大致如下:useMemo → render → useEffect

8.1.2 获取当前路由路径 useLocation

  const pathUrl = useLocation() 【注意hooks顶层调用

  const selectedKey = pathUrl.pathname 

  对应的Menu 组件【antd】 selectedKeys={selectedKey} 可以实现菜单高亮

8.1.3  useNavigate

传参见7.1.3

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

const navigate = useNavigate()

 navigate("/session-timed-out");

8.1.4 useReducer 管理多个相对关联的状态数据

import { useReducer } from "react"
import { Button } from "antd"
// 1、定义reducer函数,根据不同的action返回不同的新状态
function reducer(state,action){
    switch(action.type){
        case 'INC':
            return state+1
        case 'DEC':
            return state - 1
        case 'SET':
            return action.payload
        default:
            return state
    }
}

function App () {
// 2、组件中调用useReducer(reducer函数,初始值) 得到[state状态,dispatch修改状态方法]
    const [state , dispatch] = useReducer(reducer,0)
    return <div>
//3、dispatch函数分派一个action对象里面有type  通知reducer产生一个新的状态 使用这个新状态更新UI
        <Button onClick={()=> dispatch({type:'DEC'})}> — </Button>
        {state}
        <Button onClick={()=> dispatch({type:'INC'})}> + </Button>
        // 可以传参
        <Button onClick={()=> dispatch({type:'SET',payload:100})}> +到100 </Button>
    </div>   
}
export default App

8.1.5 useCallback

组件多次渲染时缓存函数  保证在App重新渲染的时候保持引用稳定

  const onGetSonMessage = useCallback((message) => {
    console.log(message)
  }, [])

8.1.6  useImperativeHandle

通过ref暴露子组件内部的方法

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

8.2 项目整体开发梳理

8.2.1、CRA  脚手架创建项目   

npx create-react-app react-jike

进入项目文件夹 安装依赖 启动

目录结构:【默认的可删除的有App.css; App.test.js;logo.svg;reportWebVitals.js;
setupTests.js  整理App.js 和index.js】

-src
  -apis           项目接口函数
  -assets         项目资源文件,比如,图片等
  -components     通用组件
  -pages          页面组件
  -router         路由
  -store          集中状态管理
  -utils          工具,比如,token、axios 的封装等
  -App.js         根组件
  -index.css      全局样式
  -index.js       项目入口

8.2.2、SCSS

预编译的 CSS,支持一些比较高级的语法【变量 嵌套等】,可以提高编写样式的效率,CRA接入

CRA接入scss 只需安装解析 sass 的包:npm i sass -D

8.2.3Ant Design组件库

在 create-react-app 中使用 - Ant Design

安装:npm install antd --save

导入并使用: import { Button } from 'antd' 

一些常用的:

Form表单

initialValues={{ xxx: 1 }} name为xxx的初始值是1  ;

onFinish回调提交表单 有参数是收集的表单数据.; 

Form.Item 的name是接口的字段 rules={[]}校验  ;

form={form}   const [form ] = Form.useForm()获取实例 form.setFieldsValue(res.data)回填数据【结构不对无法回填的时候 处理一下数据】

Upload上传  maxCount={1}  multiple={}控制上传数量  fileList={} 回显图片;onChange 有参数是{file:{},fileList:[]}

<Radio.Group> 单选框   onChange 有参数事件对象能拿到e.target.value 

<Table rowKey="id" columns={columns} dataSource={list} pagination={{total:count,pageSize:queryParams.per_page,onChange:onPageChange}}/>onPageChange函数中参数是点击的页码 然后重新set请求参数对象 从而重新请求数据

Popconfirm气泡确认框 包在button外面

8.2.4、router 路由  看目录7

安装 npm i react-router-dom

pages 里准备  Layout Login等基础组件

配置路由: router/index.js 引入组件,进行配置createBrowserRouter([]),导出路由实例    入口index.js:<RouterProvider router={router} />

8.2.5、配置别名路径 

8.2.5.1 webPack配置
  1. 路径解析配置(webpack),把 @/ 解析为 src/

  2. 路径联想配置(VsCode),VsCode 在输入 @/ 时,自动联想出来对应的 src/下的子级目录

 craco插件—路径解析配置

CRA【create-react-app】本身把webpack配置包装到了黑盒里无法直接修改,需要借助一个插件 - craco

①安装(开发环境-D) npm i -D @craco/craco
②根目录下创建配置文件  craco.config.js

③修改 scripts 命令

//craco.config.js
const path = require('path')
module.exports = {
    // webpack 配置
    webpack:{
        // 配置别名
        alias: {
            //  约定使用@表示src文件所在lujing
            '@': path.resolve(__dirname,'src')
        }
    }
}

// 修改命令 package.json
"scripts":{
    "start":"craco start",
    "build":"craco build"
}

联想路径配置 —路径联想配置(VsCode)

根目录下新增配置文件  jsconfig.json

// jsconfig.json
{
  "compilerOptions":{
    "baseUrl":"./",
    "paths":{
      "@/*":[
        "src/*"
      ]
    }
  }
}

8.2.5.2 Vite的路径别名配置

1、让Vite做路径解析(真实的路径转换)

2、让VSCode做智能路径提示(开发者体验)

npm i @types/node -Dnpm i @types/node -D  【ts代码会报错 需要安装node类型包】

vite.config.ts

import path from 'path'


//defineConfig里加入
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },

修改tsconfig.app.json文件

"compilerOptions":{} 里加入

"baseUrl": ".",
    "paths": {
      "@/*": [
        "src/*"
      ]
    },

8.2.6、 登录

表单校验: Form.item 组件上绑定​​​​​​name 校验规则rules ,表单提交回调等

   const onFinish=(values) => {
    console.log('Success:', values); // 可以拿到表单填写的数据 
  };

        <Form 
            validateTrigger={['onBlur']} // 统一设置字段触发验证的时机 默认是onChange
            onFinish={onFinish} // 提交表单且数据验证成功后回调事件
        >
          <Form.Item
            name="mobile" // 保持与接口字段一致
            // 多条校验规则 通过之后再校验下一条
            rules={[
                { required: true, message: '请输入手机号!' },
                { pattern:/^1[3-9]\d{9}$/ , message: '请输入正确手机号!' },
            ]}
          > 
            <Input size="large" placeholder="请输入手机号" />
          </Form.Item>
          // 省略其他
          <Form.Item>
            <Button type="primary" htmlType="submit" size="large" block>
              登录
            </Button>
          </Form.Item>
        </Form>

8.2.7、 请求封装

安装axios:  npm i axios

utils/request.js 文件 :创建 axios 实例,配置 baseURL,超时时间 ,请求拦截器(注入token),响应拦截器

import axios from "axios";

const request = axios.create({
    baseURL: 'http://geek.itheima.net/v1_0', // 根域名配置
    timeout: 5000 //超时时间
})
// 添加请求拦截器 请求发送之前拦截 插入自定义配置(参数)
request.interceptors.request.use((config)=> {
    //1.获取token
    const token = getToken()
    //2.按照后端有要求拼接token
    if(token) {
      config.headers.Authorization = `Bearer ${token}`
    }
    return config
  }, (error)=> {
    return Promise.reject(error)
})

// 添加响应拦截器 处理返回的数据
request.interceptors.response.use((response)=> {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    return response.data
  }, (error)=> {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    // 监控401 token 
    if(error.response.status === 401) {
      // 清除token 跳转登录
      clearToken()
      router.navigate('/login')
      window.location.reload()//强制刷新
    }
    return Promise.reject(error)
})

export {request}

utils/index.js 中,统一导出request 

import { request } from './request'
export { request }

apis/新建各模块的.js 统一维护

import { request } from "@/utils";

export function loginAPI (formData){
   return  request({
        url:'/authorizations',
        method: 'POST',
        data:formData
    })
}

axios和ts配合

axios提供了request泛型方法, 方便我们传入类型参数推导出接口返回值的类型。泛型参数 Type 的类型决定了 res.data 的类型

1根据接口文档创建一个通用的泛型接口类型(多个接口返回值的结构是相似的)

2. 根据接口文档创建特有的接口数据类型(每个接口有自己特殊的数据格式)

3. 组合1和2的类型, 得到最终传给request泛型的参数类型

export function getListAPI(params: ListParams){
    return request.request<ResType<ChannelRes>>({
        url:'/articles',
        params,
    })
}

8.2.8、redux 管理token 

安装 省略 具体看目录6

store/modules/user.js 

// 用户相关的状态管理
import { createSlice } from '@reduxjs/toolkit'
import { http } from '@/utils'
const userStore = createSlice({
  name: 'user',// 模块名
  // 数据状态
  initialState: {
    token:''
  },
  // 同步修改方法
  reducers: {
    setUserInfo (state, action) {
      state.userInfo = action.payload
    }
  }
})

// 解构出actionCreater
const { setUserInfo } = userStore.actions

// 获取reducer函数
const userReducer = userStore.reducer

// 异步方法封装
const fetchLogin = (loginForm) => {
  return async (dispatch) => {
    // 发送异步请求
    const res = await http.post('/authorizations', loginForm)
     // 提交同步action进行token 存入
    dispatch(setUserInfo(res.data.token))
  }
}

export { fetchLogin }

export default userReducer

store/index.js

// 组合子模块 导出store实例
import { configureStore } from '@reduxjs/toolkit'

import userReducer from './modules/user'

export default configureStore({
  reducer: {
    // 注册子模块
    user: userReducer
  }
})

index.js

import {Provider} from 'react-redux'
import store from './store'
const root = createRoot(document.getElementById('root'))
root.render(
  <Provider store={store}>
    <RouterProvider router={router}>
    </RouterProvider>
  </Provider>
)

pages/login/index.js 登录并跳转页面

import { useDispatch} from 'react-redux'
import { useNavigate } from 'react-router-dom'
import { fetchLogin } from '../../store/modules/user'

// 表单提交登录 
 const dispatch = useDispatch()
 const navigate = useNavigate()
 const onFinish= async (values) => {
    await dispatch(fetchLogin(values))
    navigate('/')
    message.success('登录成功')
  };
token持久化 

基于Redux的存储方式又是基于内存的,刷新就会丢失

①存token时 localStorage也存一份

②初始化数据的时候 先查询localStorage

封装存取方法 :因为很多模块都要用到

utils/token.js

const TOKENKEY = 'token_key'

function setToken (token) {
  return localStorage.setItem(TOKENKEY, token)
}

function getToken () {
  return localStorage.getItem(TOKENKEY)
}

function clearToken () {
  return localStorage.removeItem(TOKENKEY)
}

export {
  setToken,
  getToken,
  clearToken
}

utils/index.js 中转导出

store/modules/user.js 

    import { setToken as _setToken,getToken} from '@/utils'

    initialState: { // 数据状态
        token: getToken() || ''
    },
    reducers:{ // 同步修改方法
        setToken(state,action){
            state.token = action.payload
            _setToken(action.payload)
        }
    }

8.2.9 路由鉴权

封装 AuthRoute 路由鉴权高阶组件,判断本地是否有token,如果有,就返回子组件,否则就重定向到登录Login

components/AuthRoute/index.jsx

import { getToken } from '@/utils'
import { Navigate } from 'react-router-dom'

const AuthRoute = ({ children }) => {
  const isToken = getToken()
  if (isToken) {
    return <>{children}</>
  } else {
    return <Navigate to="/login" replace />
  }
}

export default AuthRoute

src/router/index.jsx

import AuthRoute from '@/components/Auth'
// 登录时,直接渲染相应页面组件 未登录时,重定向到登录页面
element: <AuthRoute><Layout /></AuthRoute>,

二级路由 嵌套子路由 children: [] 和Layout中配置二级路由出口<Outlet/> 省略

路由菜单交互实现useNavigate() 省略


8.2.10 获取个人信息,退出登录,token失效

获取个人信息:①在Redux的store中编写获取用户信息的相关逻辑 ②在Layout组件中触发action的执行 ③使用store中的数据进行渲染

退出登录:①添加确认回调事件 ②store中新增退出登录的action函数,在其中删除token 用户信息 ③回调事件中,调用userStore中的退出action  ④返回登录页面

token失效:一段时间不做任何操作,超时之后应该清除所有过期用户信息跳回到登录

请求接口返回401 监控这个状态 具体看8.2.7

8.2.11 echarts

获取 ECharts - 入门篇 - 使用手册 - Apache ECharts

安装之后  封装组件

import * as echarts from "echarts"
import { useEffect, useRef } from "react"
const  BarChart = ({title,xData, sData, style = { width: '400px', height: '300px' }})=> {
    const chartRef = useRef(null)
    useEffect(()=>{//  useEffect保证dom可用 才进行图表的渲染
        //1. 获取渲染图标的dom节点 要有宽高
        const chartDom = chartRef.current
        // 2. 图表初始化生成图表实例对象
        const myChart = echarts.init(chartDom)
        // 3.准备图表参数
        const option = {
            title: {
                text:title
            },
            xAxis: {
                type: 'category',
                data: xData
              },
            yAxis: {
              type: 'value'
            },
            series: [
              {
                data: sData,
                type: 'bar'
              }
            ]
        }
        myChart.setOption(option)  
    },[])
    return <div ref={chartRef} style={style}></div>
}

export default BarChart

使用
 <BarChart
        title={'三大框架数量'}
        xData={['Vue', 'React', 'Angular']}
        sData={[2000, 5000, 1000]} />

8.2.12 打包

打包指的是将项目中的源代码和资源文件进行处理,生成可在生产环境中运行的静态文件的过程打包

命令:npm run build

8.2.12.1 项目本地预览
  1. 全局安装本地服务包 npm i -g serve  该包提供了serve命令,用来启动本地服务器

  2. 在项目根目录中执行命令 serve -s ./build  在build目录中开启服务器

  3. 在浏览器中访问:http://localhost:3000/ 预览项目

8.2.12.2 打包优化-配置路由懒加载

路由的JS资源只有在被访问时才会动态获取,目的是为了优化项目首次打开的时间

// router/index.js

import { lazy , Suspense} from "react";

// import Artical from "../page/article";
const Artical = lazy(()=> import("@/page/article"))  //1. 使用 lazy 方法导入路由组件

2. 使用内置的 Suspense 组件渲

        element: (
          <Suspense fallback={'加载中'}>
            <Article />
          </Suspense>
        )

如果有报错:Import in body of module; reorder to top import/first

将导入语句移至模块顶部并置于所有业务代码之前。 所以将const Artical = lazy(()=> import("@/page/article"))放在所有import代码之后

打开控制面板-network-选择js 可以看到路由切换时加载的

8.2.12.3 打包优化-包体积可视化分析

①安装分析打包体积的包:npm i source-map-explorer

②在 package.json 中的 scripts 标签中,添加分析打包体积的命令

"scripts": {
  "analyze": "source-map-explorer 'build/static/js/*.js'",
}

③对项目打包:npm run build(如果已经打过包,可省略这一步)

④运行分析命令:npm run analyze

⑤通过浏览器打开的页面,分析图表中的包体积

8.2.12.4 打包优化-CDN优化

CDN是一种内容分发网络服务,当用户请求网站内容时,由离用户最近的服务器将缓存的资源内容传递给用户
哪些资源可以放到CDN服务器?
体积较大的非业务JS文件,比如react、react-dom
1.体积较大,需要利用CDN文件在浏览器的缓存特性,加快加载时间

2.非业务JS文件,不需要经常做变动,CDN不用频繁更新缓存

实践

1.把需要做CDN缓存的文件排除在打包之外(react、react-dom)

2.以CDN的方式重新引入资源(reac、react-dom)

craco.config.js

const path = require('path')
const { whenProd, getPlugin, pluginByName } = require('@craco/craco')
module.exports = {
  // webpack 配置
  webpack: {
    // 配置别名
    alias: {
      // 约定:使用 @ 表示 src 文件所在路径
      '@': path.resolve(__dirname, 'src')
    },
    // 配置webpack
    // 配置CDN
    configure: (webpackConfig) => {
      let cdn = {
        js:[]
      }
      whenProd(() => {
        // key: 不参与打包的包(由dependencies依赖项中的key决定)
        // value: cdn文件中 挂载于全局的变量名称 为了替换之前在开发环境下
        webpackConfig.externals = {
          react: 'React',
          'react-dom': 'ReactDOM'
        }
        // 配置现成的cdn资源地址
        // 实际开发的时候 用公司自己花钱买的cdn服务器
        cdn = {
          js: [
            'https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.production.min.js',
            'https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.production.min.js',
          ]
        }
      })

      // 通过 htmlWebpackPlugin插件 在public/index.html注入cdn资源url
      const { isFound, match } = getPlugin(
        webpackConfig,
        pluginByName('HtmlWebpackPlugin')
      )

      if (isFound) {
        // 找到了HtmlWebpackPlugin的插件
        match.userOptions.files = cdn
      }

      return webpackConfig
    }
  }
}

public/index.html
<body> 
   <div id="root"></div>  
   <!-- 加载第三发包的 CDN 链接 -->  
   <% htmlWebpackPlugin.options.files.js.forEach(cdnURL => { %>
       <script src="<%= cdnURL %>"></script>  
    <% }) %>
</body>

8.2.13 react.memo优化组件渲染

允许组件在props没有改变的情况下跳过重新渲染【默认机制:顶层组件发生重新渲染,这个组件树的子级组件都会被重新渲染 】

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

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

props的比较机制:进行的是‘浅比较’,底层使用 Object.is 进行比较,针对于对象数据类型,只会对比俩次的引用是否相等,如果不相等就会重新渲染,Object([], []) => false 有变化, 当父组件的菌数重新执行时,实际上形成的是新的数组引用

如果就想让引用确定, 那么可以用useMemo缓存

8.2.14  forwardRef

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

import { forwardRef, useRef } from 'react'
// forwardRef包裹 子组件接收 props, ref  
const MyInput = forwardRef(function Input(props, ref) {
  return <input {...props} type="text" ref={ref} />//对应的DOM上绑定ref
}, [])

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

9. clssAPI(已不推荐)

// class API
import { Component } from 'react'
// 继承react的基类Component
class Counter extends Component {
  // 状态变量state
  state = {
    count: 0,
  }

  // 事件回调
  clickHandler = () => {
    // 修改状态变量this.setState() 触发UI组件渲染
    this.setState({
      count: this.state.count + 1,
    })
  }

  // UI模版render(){}函数
  render() {
    return <button onClick={this.clickHandler}>+{this.state.count}</button>
  }
}

function App() {
  return (
    <div>
      <Counter />
    </div>
  )
}

export default App

9.1 类组件生命周期

9.2 类组件通信(依赖this)

父传子:直接通过prop子组件标签身上绑定父组件中的数据即可

                <Son msg={this.state.msg}/>  子:this.props.msg

子传父:在子组件标签身上绑定父组件中的数,子组件中调用这个函数传递参数

   onGetSonMsg = (msg) => { this.setState({ msg }) }  子组件标签上onGetSonMsg={this.onGetSonMsg}   子组件中const { onGetSonMsg } = this.props   onClick={() => onGetSonMsg('this is son msg')}

10 zustand 简洁状态管理工具

Introduction - Zustand

import { create } from 'zustand'
// 创建store 函数参数必须返回一个对象 对象内部写状态数据和方法
const useStore = create((set) => {
  return {
    count: 0,
    inc: () => {
        //  必须调用set来修改数据 两种写法①参数是函数 需要用到老数据时 
      set(state => ({ count: state.count + 1 }))
      // ②参数是一个对象
      set({ count: 1 })
    },
    channelList:[],
    // 支持异步
    getList: async ()=>{
        const res = await fetch(URL)
        const resJson = await res.json()
        set({ channelList: resJson.data.channels})
    }
  }
})

export default useStore

使用:
 import useStore from './store/useCounterStore.js'
// useStore() 返回的是一个对象 解构出状态变量和方法
 const { count, inc,channelList, getList} = useStore()  
 <button onClick={inc}>{count}</button>}

切片模式

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


import { create } from 'zustand'

// 创建counter相关切片
const createCounterStore = (set) => {
  return {
  // 省略
  }
}

// 创建channel相关切片
const createChannelStore = (set) => {
  return {
   // 省略
  }
}

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

使用:同不切分

11 Vite

开始 | Vite 官方中文文档

是一个框架无关的前端工具链, 可以快速的生成一个 React + TS 的开发环境, 并且可以提供快速的开发体验

npm create vite@latest react-ts-pro -- --template react-ts


① npm create vite@latest 固定写法 (使用最新版本vite初始化项目)

② react-ts-pro 项目名称 (可以自定义)

③ -- --template react-ts 指定项目模版位react+ts

12 Typescript

12.1 useState

自动推导:通常React会根据传入useState的默认值来自动推导类型(setXxx的时候也会校验参数类型),不需要显式标注类型

传递泛型参数:useState本身是一个泛型函数,可以传入具体的自定义类型

type User = {
  name: string
  age: number
}
//1 限制useState函数参数的初始值必须满足类型为: User | () => {return User}
const [user, setUser] = React.useState<User>({
  name: 'jack',
  age: 18
})

// 2 执行setUser 限制参数必须满足类型为:User | ()=> User | undefined
setUser(newUser)
// 这里newUser对象只能是User类型

3. 使用user状态数据具备User类型相关的类型提示

初始值为null: 当我们不知道状态的初始值是什么,将useState的初始值为null是一个常见的做法,可以通过具体类型联合null来做显式注解useState<User l null>(null)

在使用的时候 {user?.age}   ?. 是可选链运算符 当做类型守卫 null 或者 undefined) 的情况下不会引起错误

12.2 props

为组件prop添加类型,本质是给函数的参数做类型注解,可以使用type对象类型或者interface接口来做注解

// type Props = {
//   className:string
// }
interface Props  {
  className: string
}
function Button(props:Props){
  return <button className={props.className}>确定</button>
}

父组件 Button组件只能传入名称为className的prop参数,类型为string, 且为必填
 <Button className='test'></Button>

12.2.1 为children添加类型:

是一个比较特殊的prop, 支持多种不同类型数据的传入,需要通过一个内置的ReactNode类型来做注解

type Props = {
  className:string
  children: React.ReactNode
}
function Button(props:Props){
  return <button className={props.className}>{props.children}</button>
}

使用:children可以是多种类型,包括:React.ReactElement 、string、number、
React.ReactFragment 、React.ReactPortal 、boolean、 null 、undefined
 <Button className='test'>ceshi</Button>

12.2.2 事件

type Props = {
  onGetMsg?:(msg:string)=> void
}
function Button(props:Props){
  const {onGetMsg} = props
  const clickHandle = ()=>{
     onGetMsg?.("msg") // onGetMsg非必须 所以需要用?. 不然报错:不能调用可能是“未定义”的对象
  }
  return <button onClick={clickHandle}>子传父</button>
}


父组件:
   // 需要遵守类型的约束,参数传递需要满足要求 不然提示参数“msg”隐式具有“any”类型
  const getMsgHandler =(msg:string)=>{
    console.log(msg); 
  }
    // 1、绑定prop时如果绑定内联函数直接可以推断出参数类型,
 <Button onGetMsg={(msg)=>console.log(msg)}></Button>
    // 2、否则需要单独注解匹配的参数类型
 <Button onGetMsg={getMsgHandler}></Button>

12.3 useRef

获取dom的场景,可以直接把要获取的dom元素的类型当成泛型参数传递给useRef,可以推导出.current属性的类型

function Foo() {
  // 尽可能提供一个具体的dom type, 可以帮助我们在用dom属性时有更明确的提示
  // divRef的类型为 RefObject<HTMLDivElement> input的话是<HTMLInputElement>
  const inputRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    inputRef.current?.focus()
  },[])

  return <div ref={inputRef}>etc</div>
}

引用稳定的存储器:把useRef当成引用稳定的存储器使用的场景可以通过泛型传入联合类型来做,比如定时器的场景(不是很明白)

  const timeId = useRef<number | undefined>(undefined)
  useEffect(()=>{
    timeId.current = setInterval(()=>{
      console.log('12');  
    },1000)
    return ()=> clearInterval(timeId.current)
  },[])

13 视频案例用到的库 工具等

13.1. lodash

13.1.1orderBy

lodash.orderBy | Lodash中文文档 | Lodash中文网

安装 npm i --save lodash
yarn add lodash

使用 import _ from 'lodash'
// 第一个参数Array|Object 第二个参数 按什么排序 第三个参数 正序倒序
_.orderBy(users, ['user'], ['desc']);

13.1.2 groupBy分组

// 参数1:Array|Object 用来迭代的集合
// 参数2 这个迭代函数用来转换key(根据什么来分组)
_.groupBy([], ''); 

举例
_.groupBy(['one', 'two', 'three'], 'length');
// => { '3': ['one', 'two'], '5': ['three'] }

13.2. uuid

生成独一无二id

uuid - npm

安装: npm install uuid

import { v4 as uuidv4 } from 'uuid';
uuidv4()

13.3. day.js

轻量化处理时间

安装 | Day.js中文网

安装 npm install dayjs

使用:
import dayjs from 'dayjs' 
dayjs().format('MM-DD HH:-mm') // 10-30 09:07

13.4. json-server 工具

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

方式:

①前端直接写假数据(纯静态,没有服务)

②自研Mock平台

③ json-server等工具

是一个快速以json文件作为数据源模拟接口服务的工具

安装 npm install json-server -D

根目录下准备一个db.json 文件

package.json中scripts里添加
"serve": "json-server db.json --port 3004"

npm run serve  保持服务不关闭

13.5. 谷歌调试插件 

①点击谷歌右侧竖着的三个点→更多工具(more tools)→ 扩展extensions

②直接打开chrome://extensions/

③谷歌右侧有扩展的图标 点击 之后再点击Manage extensions

打开开发者模式Developer mode

安装办法

第一种、谷歌商店 搜索安装

第二种、极简插件 搜索 React Developer Tools 和 Redux DevTools 下载→解压→ 将.crx文件拖到前面打开的扩展页面→ 弹出框内选择添加 → 重启谷歌

极简插件 - 搜索插件

Redux DevTools 会提示No store found. Make sure to follow the instructions

https://github.com/zalmoxisus/redux-devtools-extension#usage 查看这个知道还要做一些配置 但是这里用的是  redux toolkit 不知如何处理

13.6. Ant Design Mobile

快速上手 - Ant Design Mobile

npm install --save antd-mobile

13.6.1 主题定制

// 1、全局 
// 根目录加一个theme.css
:root:root {
  --adm-color-primary: #a062d4;
}
// 入口文件index.js导入
import './theme.css'


//2、局部
.purple-theme {
  --adm-color-primary: #a062d4;
}

<div className='purple-theme'>
  <Button color='primary'>Purple</Button>
</div>

13.6.2 List无限滚动

1. 滑动到底部触发加载下一页动作

<InfiniteScroll/> loadMore回调函数加上async

滚动条件:① hasMore为true 2 距离底部距离<threshold

2. loadMore加载下一页数据

3. 把老数据和新数据做拼接处理

[...oldList, ... newList]

4. 停止监听边界值

没有数据了将hasMore设置为false

13.7. 样式reset  normalize.css

安装: npm install normalize.css

入口文件中引入:import 'normalize.css';

13.8. 富文本编辑器 react-quill

npm i react-quill@2.0.0-beta.2 【特定版本兼容react18 如果失败尝试npm i react-quill@2.0.0-beta.2  --legacy-peer-deps】

import ReactQuill from 'react-quill'
import 'react-quill/dist/quill.snow.css'

  <ReactQuill
          className="publish-quill"
          theme="snow"
          placeholder="请输入文章内容"
        />

13.9. 引入汉化包 时间选择器显示中文

import locale from 'antd/es/date-picker/locale/zh_CN'

            {/* 传入locale属性 控制中文显示*/}
            <RangePicker locale={locale}></RangePicker>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值