react18学习记录

152 阅读17分钟

image-20240205171022675

react第一天

创建项目

全局安装包create-react-app:npm install -g create-react-app

创建新的项目:create-react-app 项目名

启动项目:npm run start

JSX

JSX: javascript+xml。在js中编写HTML模板结构。是js的语法扩展,需要解析工具解析之后才能在浏览器中运行。

key的作用

react框架内部使用,提升页面的更新性能

事件绑定

on + 事件名称

点击事件

获取event对象

function click(e){
    console.log(e)
}
function clickTwo(a, e){
	console.log(e)
}
// 没有实参
<button onClick="handleClick">按钮1</button>
// 有实参,要用箭头函数,否则会立即执行
<button onClick="(e)=>handleClick('a', e)">按钮2</button>

useState

向组件添加一个状态变量,从而控制组件的渲染效果。数据驱动视图

useState是一个函数,返回值是一个数组。数组的第一个值是状态变量;第二个值是一个函数,用来修改状态变量的。

useState的参数将作为状态变量的初始值。如下,count初始值就是 0

function App() {
  const [ count, setCount ] = useState(0);
  const [obj, setObj ] = useState({name: 'jerry'})
  function counterClick(){
    setCount(count+1)
  }
  function switchName(){
    setObj({
      ...obj,
      name: obj.name === 'lucy' ? 'jerry' : 'lucy'
    })
  }
  return (
    <div className="App">
      <button onClick={counterClick}>{count}</button>
      <div>{ obj.name }</div>
      <button onClick={switchName}>切换name</button>
    </div>
  );
}

修改状态的规则

状态不可变

状态是只读的,应该替换它而不是修改它。直接修改不能引发视图更新。对于对象类型的状态变量,应该始终传给set方法一个全新的对象来进行修改。

样式

React组件基础的样式控制有两种方式:

  • 方式1:直接写在行内
  • 方式2:class类名控制
// 方式1
<div style={{color: 'red'}}>我是div</div>
// 方式2
// index.css
.foo{
    color: red
}
import './index.css';
function App(){
    return (
    	<div className="foo">我是div</div>
    )
}

react第二天

useRef

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

第一步:使用useRef创建ref对象,并与jsx绑定

第二步:在DOM可用时,通过ref对象的current拿到Dom对象

import { useRef } from "react"

export default function Ref(){
    const btnRef = useRef(null);
    const handleBtnClick = () => {
        console.log( btnRef.current );
    };
    return (
        <button onClick={handleBtnClick} ref={btnRef}>获取ref</button>
    )
}

组件通信

1.父子组件通信

实现步骤

1.父组件传递数据 - 在子组件标签上绑定数据

2.子组件接收数据 - 子组件通过props参数接收数据(函数里接收一个props参数)

props可以传递任意类型的值,是一个只读对象。在子组件中不能进行修改,父组件的数据只能由父组件进行修改

特殊的props:children

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

3.子传父:在子组件中调用父组件的函数,并传递实参

image-20240123145800786

2.兄弟组件通信

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

(就是改父组件的数据,父组件的数据改了之后,引用改数据的子组件也会同步的发生变化。兄弟组件引用的是同一个数据源)

3.跨层组件通信

使用context机制。

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

2.在顶层组件(App)中通过Ctx.Provider(是一个高阶组件,不是函数)提供数据。Provider接收一个value属性,当value变化时候,内部的所有组件都会被重新渲染

3.在底层组件(B)中通过useContext(使用上下文对象)钩子函数取使用数据。useContext函数的参数必须是定义的context自身

image-20240123155521056

useContext

用于跨组件之间的通信。代码示例所示。主要分为三步:

1)用createContext创建一个上下文:MyContext

2)使用context提供的高阶组件Provider,用value属性注入要传递的值:<MyContext.Provider value={value}></MyContext.Provider>

3)使用useContext使用注入的值,useContext的入参必须为创建的上下文本身(MyContext):const value = useContext(MyContext)。

react第三天

useEffect

1.用法说明

用于在React组件中创建不是由事件引起而是由渲染本身引起的操作,比如发送ajax请求,更改DOM等例如页面加载完毕后掉服务。

语法:useEffect(()=>{ *** }, [])

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

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

2.useEffect依赖项说明

  • 没有依赖项 -- 组件初始渲染+组件更新时执行

    image-20240124101912232

  • 空数组依赖项 -- 只在组件渲染时执行一次

  • 添加特定依赖项 -- 组件初始渲染 + 特性依赖项变化时执行

    image-20240124102515070

3.useEffect -- 清除副作用

在useEffect中return出一个函数,执行卸载时候的清理操作

image-20240124103359598

代码示例:

image-20240124104438255

自定义hook

1.声名一个以use开头的函数

2.在函数体内封装可复用的逻辑

3.把组件中要用到的数据和函数return出去

4.在组件里调用这个函数,解构出来状态和回调函数进行使用

image-20240124112223599

知识点补充

1.react中的fetch方法?

参考文档:www.python100.com/html/116010…

image-20240124094754161

2.利用axios和json-server模拟接口请求?

1.安装axios和json-server插件

2.在根目录下创建json文件(如 db.json),里边存放模拟请求的数据

3.在package.json里面的script对象里添加配置:"server": "json-server db.json --port 5200"

4.启动命令:npm run server,就可以启动本地服务:http://localhost:5200

5.axios调用这个服务,获取到模拟数据

react第四天

react-redux & reduxjs/toolkit

redux:集中状态管理工具,可以独立于框架运行。

本专题使用reduxjs/tooltkit 和 react-redux来实现redux和react的融合

npm install @reduxjs/toolkit react-redux

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

dispatch:用来派发action的事件

RTK:redux toolkit

CRA:create-react-app

不理解的点记录

1.reduxjs/toolkit异步修改操作

image-20240126095624115

使用步骤记录

以实现计数器为例

1)先安装reduxj/toolkit 和 react-redux插件

2)创建一个store文件夹,包含modules文件夹和index.js文件

3)在module文件夹里创建counterReducer.js文件

4)引入createSlice函数并调用

5)解构出action方法

6)创建counterReducer

7)导出供外部使用

8)在index.js文件里引入counterReducer,导入configureReducer函数,组合一个根store并导出

9)在根组件index.js里使用react-redux的高阶组件Provider,传入store

10)在counter.js业务组件里,引入步骤5导出的action

11)在业务组件里引入useSelector,使用store中的状态数据

12)在业务组件中引入useDispatch,生成一个dispatch,用于提交action

完整示例代码如下:

import { createSlice } from '@reduxjs/toolkit';
const counterStore = createSlice({
    name: 'counter',
    initialState: {
        count: 0
    },
    reudcers: {
        increment: (state, action){
            state.count += action.payload;
        },
    	decrement: (state){
    		state.count --;
        }
    }
})
const { increment, decrement } = counterStore.actions;
const counterReducer = counterStore.reducer;
export { increment, decrement };
export default counterReducer;

// index.js store根文件
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './modules/counter.js';
const store = configureStore({
    reducer: {
        counter: counterReducer
    }
});
export default store;

// index.js 根文件
import { Provider } from 'react-redux';
import store from './store/index.js';
<Provider store={store}>
	<App />
</Provider>

// counter.js 业务组件
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './store/module/counterReducer.js';
export default function Counter(){
    const dispatch = useDispatch();
    const { count } = useSelector( state => state.counter );
    return (<div>
        <button onClick={()=>dispatch(decrement())}>-</button>
            { count }
        <button onClick={()=>dispatch(increment(2))}>-</button>
    </div>)
}

react-router-dom

路由导航

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

声名式导航

在模板中通过react-router-dom提供的Link组件描述要跳到哪里去。Link组件会被解析成a标签,to属性会被解析成href属性。如需要传参,直接进行字符串拼接。可以用在菜单配置等场景

编程式导航

使用react-router-dom提供的钩子useNavigate得到导航方法,然后以命令式的方式进行页面跳转。更加灵活。在js代码里执行跳转

import { Link, useNavigate } from "react-router-dom";
function App() {
  const navigate = useNavigate();
  return (
    <div className="App">
      // 声名式导航
      <Link to="/list">列表</Link>
      // 编程式导航
      <div onClick={()=>navigate('/detail')}>跳到详情页</div>
    </div>
  );
}

export default App;

额外记录

createBrowserRouter创建的对象,也有一个navigate属性,可以做页面跳转

router.navigate('/')

路由导航传参

searchParams传参

1.跳转代码里路由上拼接参数,?方式拼接

2.接收时使用useSearchParams()函数,解构出params

3.用params.get(key)方法指定要取的入参字段

// 源文件
import { useNavigate } from 'react-router-dom';
export default function App(){
    const navigate = useNavigate();
    return (
    	<div onClick={()=>navigate('/detail?id=1')}>searchParams传参</div>
    )
}
// 目标文件
import { useSearchParams } from 'react-router-dom';
export default function Detail(){
    const [ params ] = useSearchParams();
    const id = params.get('id');
}

params传参

1.路由配置里预留动态参数的位置。路由占位符

2.跳转代码里,参数拼在路由上,斜杠拼接

3.接收时用useParams()接收。key值与动态路由上的要保持一致

// 路由文件
import { createBrowserRouter } from 'react-router-dom';
import Detail from './pages/detail';
const router = createBrowserRouter([
    {
        path: '/detail/:id',  // 路由的动态参数
        element: <Detail />
    }
])
// 源文件
import { useNavigate } from 'react-router-dom';
export default function App(){
    const navigate = useNavigate();
    return (
    	<div onClick={()=>navigate('/detail/1')}>searchParams传参</div>
    )
}
// 目标文件
import { useParams } from 'react-router-dom';
export default function Detail(){
    const params = useParams();
    const id = params.id;
}

嵌套路由

在一级路由中又嵌套了其他路由,这种关系就叫做嵌套路由。

嵌套到一级路由内的路由又称作二级路由。

1.使用children属性配置路由嵌套关系

2.使用组件配置二级路由的渲染位置

image-20240129101333884

配置默认二级路由

{
    children: [
        {
            index: true,  // 默认二级路由
            element: <List />
        },
        ...
    ]
}

路由懒加载

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

如何配置?

1.把路由修改为React提供的lazy函数进行动态导入

2.使用React内置的Suspense包裹路由中的element选项对于的组件

import { lazy, Suspense } from 'react';
// 引入懒加载文件
// es6 import()。 import命令式同步引入,import()函数动态引入
const Home = lazy(()=>import('@/pages/Home'));
const store = createBrowserRouter([
    // 使用Suspense的时候,一定要定义fallback的内容,是组件加载中占位的内容。可以是一个封装的组件。为空的话会报错
    {path:'/', element:<Suspense fallback={'加载中'}}><Home/></Suspense>
])

404路由配置

以*作为路由的path

{
    path: '*',
    element: <NotFound />
}

两种路由模式

history

  • 由createBrowserRouter创建
  • 表现:url/login
  • 底层原理:History对象 + pushState事件
  • 需要后端支持

hash

  • 由createHashRouter创建
  • 表现:url/#/login
  • 底层原理:监听hashChange事件
  • 不需要后端支持

核心API

  • createBrowserRouter
  • createHashRouter
  • useNavigate
  • useSearchParams
  • useParams
  • RouterProvider组件
  • Link组件
  • Outlet组件

react第五天

useMemo

用于消耗非常大的计算,优化计算密集型的操作或者昂贵的函数调用。接收一个计算函数和一个依赖项作为入参,并返回计算结果。

只有在依赖项发生变化的时候才会进行重新计算,没有变化就会使用上一次的计算结果。提高性能。

import { useMemo } from 'react';    
const memorizedValue = useMemo(()=>{
    // 计算函数。应当是纯函数,不应有其他任何副作用。如果执行副作用或者异步操作,推荐使用 useEffect 钩子函数
    // 返回计算结果
}, [dependency1, dependency2,...])

react第六天

配置@别名路径

利用craco插件。

  • 1)引入craco插件: @craco/craco

  • 2)配置craco.config.js文件(配置别名信息)

  • 3)配置jsconfig.json文件(vscode使用,用来联想出路径)

  • 4)更改项目启动命令

    // craco.config.js
    const path = require('path');
    module.exports = {
        // webpack配置
        webpack: {
            // 配置别名 
            alias: {
                // 约定:使用 @ 表示 src 文件所在路径
                "@": path.resolve(__dirname, 'src')
            }
        }
    }
    // jsconfig.json
    {
        "compilerOptions": {
            "baseUrl": "./",
            "paths": {
                // 联想,* 是通配符
                "@/*": [
                    "src/*"
                ]
            }
        }
    }
    // 启动命令
    "start": "craco start",
    "build": "craco build",
    

Token持久化

redux中存储的数据会随着刷新浏览器而被重置。redux是基于浏览器内存的存储方式。

持久化:

1.获取时候存储两份,redux里和localStorage里都存储一份

2.redux初始化的时候,优先从localStorage里取,取不到再赋值为空值

处理Token失效

使用Token做路由权限控制(高阶组件)

image-20240131163342855

// 封装高阶组件:高阶组件就是一个函数,接收一个组件为入参,返回一个新的组件
const { getToken } = require("@/utils");
const { Navigate } = require("react-router-dom");
// children:高阶组件包裹的组件
const AuthRoute = ({children}) => {
    const token = getToken();
    if(token){
        return <>{children}</>  // 直接return children 也生效
    }else{
        // Navigate重定向 replace 直接替换,不要记录
        return <Navigate to="/" replace />
    }
}
export default AuthRoute;

// 使用高阶组件
import { createBrowserRouter } from "react-router-dom";
import Login from "@/pages/Login";
import Layout from "@/pages/Layout";
import AuthRoute from '@/components/AuthRoute';

const router = createBrowserRouter([
    {
        path: '/',
        element: <Login />
    },
    {
        path: '/index',
        element: <AuthRoute><Layout/></AuthRoute>
    }
])

export default router;

react第七天

useLocation

获取路由相关信息

import { useLocation } from 'react-router-dom';
const location = useLocation();

useReducer

useReducer是useState的替代方案,接收一个reducer和初始值为入参,返回当前数据状态和配套更改数据的dispatch方法。(理解类似于redux)

reducer是一个纯函数,没有任何UI和副作用。输入固定的值,返回固定的值。

import { Button, Space } from "antd";
import { Fragment, useReducer } from "react";

const UseReducerComponent = () => {
    // 定义一个reducer函数,根据不同的指令返回不同的状态
    const reducer = (state, action) => {
        // action 是触发更改时候接收的参数
        switch(action.type){
            case 'DEC':
                return state - 1;
            case 'INC':
                return state + 1;
            case 'SET':
                return action.payload;
            default:
                return state
        }
    }
    // 组件中通过useReducer使用reducer函数,并设置state的初始值。解构出新的状态以及改变状态的方法
    const [ state, dispatch ] = useReducer(reducer, 0)
    return <Fragment>
        {/* 触发不同的指令,根据type来作区分 */}
        <Button onClick={()=>dispatch({type:'DEC'})}>-</Button>
        <Space>{ state }</Space>
        <Button onClick={()=>dispatch({type:'INC'})}>+</Button>
        <Button onClick={()=>dispatch({type: 'SET', payload: 10})}>加到10</Button>
    </Fragment>
};

export default UseReducerComponent;

React.memo

缓存子组件,只有在子组件的props发生改变的时候,才会重新渲染。

react渲染机制:父组件重新渲染,就会导致子组件重新渲染。浪费性能。

使用方式:memo包裹住子组件

import { memo } from "react";

const Son = (props) => {
    console.log('son重新渲染了');
    return <div>我是子组件</div>
}

export default memo(Son);

props的比较机制

使用memo缓存组件之后,React会对每一个props的值使用Object.is来比较新值和老值,返回true则表示没有变化

对于基础类型,值不变就不变。

对于引用类型,要保证引用地址不变。父组件重新渲染时候,父组件内的引用类型数据实际上都是新的引用类型了。

对于引用类型,可以使用useMemo来缓存下来,这样父组件每次重新渲染的时候,依赖项不变的话,引用类型也就不会发生变化了

const list = useMemo(()=>{
 return [1,2,3]
}, [])
return <Fragment>
 <Card>
     <Son 
         value={list}
         />
     <hr />
     <Button onClick={()=>setCount(count+1)}>+</Button>
 </Card>
</Fragment>

axios

get 传参 params

post 传参 data

打包和本地预览

npm run build 打出一个静态包

npm i -g serve 全局安装serve

serve -s build 通过静态服务器模拟生产服务器运行项目的过程

打包优化

  • 配置路由懒加载

包体积分析

使用第三方插件:source-map-explorer

1.安装:npm i source-map-explorer

2.配置package.json: "analyze":"source-map-explorer 'build/static/js/*.js'" (指定路径,示例为分析js文件夹下所有的js文件中包的大小占比)

3.启动命令:npm run analyze

4.找出占比较大的包,进行优化处理

CDN优化

CDN:是一种内容分发网络服务,当用户请求网站内容时,由离用户最近的服务器将缓存的内容资源传递给用户

适用于:体积较大的非业务js代码,例如react、react-dom等。

1)体积较大,需要利用CDN文件在浏览器的缓存特性,加快加载时间。

2)非业务js文件,不需要经常变动,CDN不用频繁更新缓存。

项目中如何做?

1)把需要CDN缓存的文件排除在打包之外。

2)以CDN的方式重新引入资源

react第八天

useCallback

在组件多次重新渲染的时候缓存函数

// useCallback里接收一个回调函数,一个依赖项
useCallback(()=>setCount(count+2), [count]);

forwardRef

能够将父组件的ref属性传递给下层组件里使用。

//父组件
import { Button, Card } from "antd";
import { Fragment, useMemo, useRef } from "react";
import SonRef from "./compoennts/SonRef";
const Hooks = () => {
    const sonRef = useRef(null);
    const getRef = () => {
        console.log( sonRef.current );
    }
    return <Fragment>
        <Card>
            <h3>forwardRef</h3>
            <SonRef ref={sonRef} />
            <button onClick={getRef}>获取ref</button>
        </Card>
    </Fragment>
}
export default Hooks;
//子组件
import { forwardRef, memo } from "react";
 // ref 和 props是同级的数据,没嵌套关系
const Son = (props, ref) => {
    return <div>
        <input ref={ref} />
    </div>
}
export default memo(forwardRef(Son));

useInperativeHandle

通过ref暴露子组件中的方法,供父组件来调用

注意点:

  • 方法接收两个参数
    • ref:来自父组件
    • 函数:里包含要暴露出的方法
  • 父组件和子组件都要绑定一个ref。
    • 父组件的ref属性是找到子组件的
    • 子组件的ref属性是找到DOM节点的

image-20240204142624972

react第九天

class的模式

生命周期

  • componentWillMount
  • componentDidMount
  • componentWillReceiveProps
  • shouldComponentUpdate
  • componentWillUpdate
  • componentDidUpdate
  • componentWillUnmount

组件间通信

同hook模式下的组件间通信。取props的时候有点差别,hook是通过传参props来获取,class是通过this.props来获取。

image-20240205091522266

zustand--极简状态管理工具

参考文档:www.jianshu.com/p/516c85c50…

react第十天

react 搭配 typescript

useState

import { Fragment, useState } from "react"
const UseStateComponent = () => {
  // 定义数据类型
  type User = {
    name: string | undefined,
    age: number
  }
  const [ count, setCount ] = useState(0);
  // 限定user的类型,为 User定义的或者是null
  const [ user, setUser ] = useState<User | null>(null);
  const add = () => {
    setCount(count + 1)
  }
  const grow = () => {
    setUser({
      name: user?.name,
      age: (user?.age || 0) + 1
    })
  }
  return (<Fragment>
    <button onClick={add}>{count}</button>
    {/* 类型守卫 */}
    <div>{ user?.name }{ user?.age }</div>
    <div onClick={grow}>长大</div>
  </Fragment>)
}
export default UseStateComponent;

props

要注意children 和 事件 的类型约束方式

事件时候,父组件的两种调用方式,参数会有一些差别。非内联函数要指定参数的类型

// 父组件
<PropsComponent
    className="parentClass"
    value="parent"
    // 绑定props时,如果非内联函数,则需要对参数的类型做注解
    // onGetMsg={handleGetMSG}
    // 如果是内联函数,则可直接推断出参数类型,不需要额外注解
    onGetMsg={(msg, num)=>console.log(msg, num)}
    >
    <div>我是propsComponent内的内容</div>
</PropsComponent>

// 子组件
// 做注解 用type对象 或者 interface接口都可以
interface Prop{
    className: string,
    value: string,
    // 特殊的参数 children(包裹在组件内的代码)
    children: React.ReactNode,
    // 事件props,重点在与对函数参数的注解   void指函数没有返回值
    onGetMsg?:(msg: string, num: number) => void
    // 可选参数
    title?: string
}
const propsComponent = (props: Prop) => {
    const { className, value, children, onGetMsg } = props;
    return <div className={ className }>
        父组件传过来的value{ value }
        {/* 事件可能为undefined,需要做一个守卫 */}
        {/* 组件内调用时候,需要遵守参数类型的约束 */}
        <button onClick={()=>onGetMsg?.('btnClick', 10)}>click me</button>
        { children }
    </div>
}
export default propsComponent;

useRef

1.获取dom元素

2.引用稳定存储器

import { useRef,useEffect } from 'react';
const UseRefComponent = () => {

    // 1.useRef搭配ts:-- 获取DOM

    // 把需要获取的dom元素的类型当成泛型参数传递给useRef。可以推导出.current属性的类型
    // 如下类型的约束,即 inputRef只能绑定到input元素上
    const inputRef = useRef<HTMLInputElement>(null);
    useEffect(()=>{
        console.log( inputRef.current );
    }, [])


    // 2.useRef搭配ts -- 引用稳定的存储器

    // 泛型传入联合类型
    const timerRef = useRef<number|undefined>(undefined);
    useEffect(()=>{
        // 利用ref的current属性,存储一个计时器
        timerRef.current = setInterval(()=>{
            console.log(1)
        }, 1000);
        // 卸载的时候清楚定时器
        return ()=>clearInterval(timerRef.current);
    }, []);
    return <div>
        <input ref={inputRef}></input>
    </div>
}

export default UseRefComponent;

react+vite 配置别名

1.要安装 types/node -- 搭配ts使用的时候要安装

npm i @types/node -D

2.配置别名--vite.config.js

resolve: {
    alias: {
      "@": path.resolve(__dirname, './src')
    }
  }

3.配置路径联想 -- tsconfig.json

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

axios + ts

image-20240205161612677

类型断言:

image-20240205170249127

把一段代码插入到html里:dangerouselySetInnerHTML

<div dangerouslySetInnerHTML={{
    __html: detail.content
}}></div>

antd组件使用记录

Form

  • initialValues 设置初始值

  • onFinish :htmlType为submit的按钮点击触发,入参为表单值

  • setFiledsValue: 设置表单的值

    // 获得form实例,绑定到form身上,在调用实例的setFiledsValue方法赋值
    const [ form ] = Form.useForm();
    form.setFiledsValue(data);
    <Form
    	form={form}
    >
    </Form>
    

知识点补充

1.什么是高阶组件?

高阶组件就是一个函数,他接收一个组件作为参数,返回一个新的组件。

2.useState和useRecuder

react 汇总

钩子函数汇总

参考文档:zhuanlan.zhihu.com/p/378392264

注意:1)只能在组件内使用 2)在组件的顶层引入,不能在if 芙蓉

react提供的钩子函数:

useState

useRef

useContext

useEffect

useMemo

React.memo

React.forwardRef

createContext

Context.Provider组件

react-redux提供的钩子函数:

useSelector

useDispatch

Provider组件

reduxjs/toolkit提供的函数:

createSlice

configureStore

react-router-dom

useNavigate

useLocation

createBrowserRouter

createHashRouter

RouterProvider组件

Link组件

Navigate组件

好用的js库汇总:

day.js:dayjs.fenxianglu.cn/category/#n…

npm install --save dayjs

moment.js: momentjs.cn/

npm install --save moment

import moment from 'moment';

lodash: www.lodashjs.com/

npm install --save lodash

import _ from 'lodash';

classnames: www.npmdoc.org/classnamesz…

npm install --save classnames

import classNames from 'classnames';

<span className={classNames('tabItem', {'activeTab': tabType === 'time'})}>最新

Normalize.css:重置浏览器默认样式

npm install normalize.css

问题记录

1.useCallback里设置状态数据,只能生效一次?

要添加依赖项。

import { Button, Card } from "antd";
import { Fragment, useCallback, useMemo, useState } from "react";
import UseReducerComponent from './compoennts/UseReducerComponent';
import Son from "./compoennts/Son";
const Hooks = () => {
    const [ count, setCount ] = useState(0);
    const handleSonClick = useCallback(()=>setCount(count+2), [count]);
    return <Fragment>
        <Card>
            <Son
                handleClick={ handleSonClick }
            />
            <Button onClick={()=>setCount(count+1)}>+</Button>
        </Card>
    </Fragment>
}

export default Hooks;

2.react生命周期函数?