React 是一个用于构建用户界面的库。React 不是一个框架——它的应用甚至不局限于 Web 开发,它可以与其他库一起使用以渲染到特定环境。例如,React Native 可用于构建移动应用程序;React 360 可用于构建虚拟现实应用程序…

React

1.搭建开发环境

create-react-app快速搭建开发环境

npx create-react-app react-basic(项目名)

node_modules:存放项目所依赖的一些第三方包文件

public:静态资源文件夹

​ favicon.ico:导航图标

​ index.html:项目首页的html模版

​ logo192.png&logo512.png:两个logo图片

​ manifest.json:应用加壳配置文件,在手机上描述我们应用程序的json文件

​ robots.txt:爬虫协议文件

src:源码文件夹

​ App.css:App组件的样式

​ App.js:App组件

​ App.test.js:自动化测试文件,用于给App做测试

​ index.css:全局样式

​ index.js:入口文件,所用组件都会通过index.js载入

​ logo.svg:react的一个标志文件,sug文件是纯粹的XML,保证了在放大缩小时图像不失真

​ reportWebVitals.js:导入了一个web-vitals的第三方库,用来检查应用程序的性能

​ setupTests.js:从Jest-dom导入匹配器,在进行测试时使用

其它文件

​ .gitignore:git的一个文件,在这里可以配置不想提交到版本库里的文件

​ package-lock.json:项目安装包的版本号

​ package.json:存放react的一些依赖和指令

​ README.md:项目的介绍文件,运用markdown标记语言编写,在github等开源代码网站上,项目常用.md文件来进行介绍

2.JSX

JavaScript和XML缩写,表示JS代码中编写HTML模板结构

jsx需要解析工具做解析才能渲染

function App() {
  return (
    <div className="App">
      this is a app
    </div>
  );
}

212417.png

识别js表达式

let count = 10;
let getname = ()=>{
  return "Ha"
}
function App() {
  return (
    <div className="App">
      this is a app
      {/* 使用引号传递字符串 */}
      {'test'}
      {/* 识别变量 */}
      {count}
      {/* 函数调用 */}
      {getname()}
      {/* 方法调用 */}
      {new Date.now()}
      {/* 使用js对象 */}
      <div style={{color:'red'}}>this is a test</div>
    </div>
  );
}

3.动态渲染列表

map循环结构,加上唯一key值

let list = [
    {id:'1001',name:'vue'},
    {id:'1002',name:'react'},
    {id:'1003',name:'angular'}
  ]
function App() {
  return (
    <div className="App">
      <ul>
        /*{list.map(item=><li>{item.name}</li>)}*/
      	{list.map(item=><li key={item.id}>{item.name}</li>)}
      </ul>
    </div>
  );
}

export default App;

4.条件渲染

逻辑运算符,三元表达式

//简单情况的条件渲染
{isLogin && <span>this is a sapn</span>}
{isLogin ? <span>this is a span</span> : <div>this is a div</div>}
//复杂情况的条件渲染
使用函数判断并返会jsx

5.事件绑定

on+事件名称 = {事件处理程序}

function App() {
  const func=()=>{
    console.log("test")
  }
  //事件参数e
	const func=(e)=>{
    console.log("test",e)
  }
  return (
    <div className="App">
      <div onClick={func}>click me</div>
    </div>
  );
}

传递参数 使用箭头函数调用

const test ="Hello React"
function App() {
  const func=(test)=>{
    console.log(test)
  }

  return (
    <div className="App">
      <button onClick={()=>func(test)}>click me</button>
    </div>
  );
}

既要传递自定义参数,又要事件对象e

const test ="Hello React"
function App() {
    //顺序与调用参数顺序相同
  const func=(test,e)=>{
    console.log(test+"____"+e)
  }

  return (
    <div className="App">
      <button onClick={(e)=>func(test,e)}>click me</button>
    </div>
  );
}

6.组件

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

function Button(){
  return <button>click me</button>
}
const button = ()=>{
    return <button>click me</button>
}
function App() {
  return (
    <div className="App">
      <Button/>
    </div>
  );
}

可以使用自闭合标签或成对标签

7.useState

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

相当于Vue3的reactive和ref 进行响应式

import {useState} from 'react'
function App() {
  const [count,setCount] = useState(0)
  const func = ()=>{
    setCount(count+1);
  }
  return (
    <div className="App">
      <button onClick={()=>func()}>click me</button>
    </div>
  );
}

export default App;

状态不可变

使用setCount函数改变Count

设置对象

const func = ()=>{
  setCount({
  ...user,
  name:tom
  });
}

设置数组

setTodos([{
    id: todos.length,
    text: text
}, ...todos]);

8.小案例

import {useState} from 'react'
let list = [
    {id:'1001',name:'vue',author:"richu"},
    {id:'1002',name:'react',author:"lisi"},
    {id:'1003',name:'angular',author:"zhangsan"}
  ] 
const user = {
    uid:1001,
    avatar:"https://www.???.com",
    uname:"richu"
}

function App() {
    const [commentslist,SetCommentslist] = useState(list)
    const deleteComments = (author)=>{
        if (author === user.uname){
            SetCommentslist(list.filter( item=> item.author !== author))
        }
    }
    return (
        <div className="App">
            <div>{user.uname}</div>
            <input placeholder='发表评论'></input>
            <div>{commentslist.map(item=><div key={item.id}>
                <span>{item.name}</span>
                <button onClick={()=>deleteComments(item.author)}>删除</button>
                </div>)}</div>
        </div>
    );
} 
export default App;

动态绑定类名

<div className={`${name=user.name && 'active'}`}></div>

<div className={className('item',{active:type === item.type })}></div>

active为类名 为true类名显示

9.受控表单绑定

使用React组件的状态(useState)控制表单的状态,双向绑定

通过value属性绑定react事件

绑定onChange事件,通过事件参数e拿到输入框最新的值 反向修改到react状态

import {useState} from 'react'

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

10.React获取Dom

使用useRef钩子函数

1.使用useRef创建ref对象,并与JSX绑定

import {useState,useRef} from 'react'

function App() {
    const [commentslist,SetCommentslist] = useState(list)
    const [value,SetValue] = useState('')
    const inputRef = useRef(null)
    const deleteComments = (author)=>{
        if (author === user.uname){
            SetCommentslist(list.filter( item=> item.author !== author))
            console.log(value)
        }
    }
    const test  = () => {
        console.log(inputRef.current)
    }
    return (
        <div className="App">
            <div>{user.uname}</div>
            <input placeholder='发表评论'></input>
            <div>{commentslist.map(item=><div key={item.id}>
                <span>{item.name}</span>
                <button onClick={()=>deleteComments(item.author)}>删除</button>
                </div>)}</div>
            <input value={value} onChange={(e)=>SetValue(e.target.value)} type='text'></input>
            <input ref={inputRef}></input>
            <button onClick={()=>test()}>click me!</button>
        </div>  
    );
} 
export default App;

2.在DOM可用时,通过inputRef.current拿到DOM对象

const test  = () => {
    console.log(inputRef.current)
}

3.动态获取input的value

import {useState,useRef} from 'react'
let list = [
    {id:'1001',name:'vue',author:"richu"},
    {id:'1002',name:'react',author:"lisi"},
    {id:'1003',name:'angular',author:"zhangsan"}
  ] 
const user = {
    uid:1001,
    avatar:"https://www.???.com",
    uname:"richu"
}

function App() {
    const [commentslist,SetCommentslist] = useState(list)
    const [value,SetValue] = useState('')
    const inputRef = useRef(null)
    const [a,SetA] = useState(4);
    const deleteComments = (author)=>{
        if (author === user.uname){
            SetCommentslist(list.filter( item=> item.author !== author))
        }
    }
    const test  = () => {
        SetCommentslist([
            ...commentslist
            ,{
            id:'100'+a.toString(),
            name:value,
            author:user.uname
        }])
        SetA(a+1)
        SetValue('')
    }
    return (
        <div className="App">
            <div>{user.uname}</div>
            <input value={value} placeholder='你怎么看' onChange={(e)=>SetValue(e.target.value)}></input>
            <button onClick={()=>test()}>发布</button>
            <div>{commentslist.map(item=><div key={item.id}>
                <span>{item.name}</span>
                <button onClick={()=>deleteComments(item.author)}>删除</button>
                </div>)}</div>
        </div>  
    );
} 
export default App;

4.实现重新聚焦

import {useState,useRef} from 'react'
let list = [
    {id:'1001',name:'vue',author:"richu"},
    {id:'1002',name:'react',author:"lisi"},
    {id:'1003',name:'angular',author:"zhangsan"}
  ] 
const user = {
    uid:1001,
    avatar:"https://www.???.com",
    uname:"richu"
}

function App() {
    const [commentslist,SetCommentslist] = useState(list)
    const [value,SetValue] = useState('')
    const inputRef = useRef(null)
    const [a,SetA] = useState(4);
    const deleteComments = (author)=>{
        if (author === user.uname){
            SetCommentslist(list.filter( item=> item.author !== author))
            /* console.log(value) */
        }
    }
    const test  = () => {
        SetCommentslist([
            ...commentslist
            ,{
            id:'100'+a.toString(),
            name:value,
            author:user.uname
        }])
        SetA(a+1)
        SetValue('')
        inputRef.current.focus()
    }
    return (
        <div className="App">
            <div>{user.uname}</div>
            <input ref={inputRef} value={value} placeholder='你怎么看' onChange={(e)=>SetValue(e.target.value)}></input>
            <button onClick={()=>test()}>发布</button>
            <div>{commentslist.map(item=><div key={item.id}>
                <span>{item.name}</span>
                <button onClick={()=>deleteComments(item.author)}>删除</button>
                </div>)}</div>
        </div>  
    );
} 
export default App;

11.组件通信

1.父传子

组件之间的数据传递,父传子实现

function Son(props){
    console.log(props)
    return <div>this is son</div>
}

function App(){
    const name = "hahah"
    return <div>
        <Son name={name}/>
    </div>
}

export default App;

props可传递任意的数据

  • 数字,字符串,布尔值,数组,对象,函数,JSX

props是只读对象,子组件不能修改

2.children属性

function Son(props){
    console.log(props)
    return <div>this is son</div>
}

function App(){
    return <div>
        <Son><span>this is a span</span></Son>
    </div>
}

export default App;

3.子传父

父组件给子组件传递函数,子组件调用并传递参数

function Son({GetMSG}){
    const test = "test"
    return <button onClick={()=>GetMSG(test)}>click me !!!!!</button>
}

function App(){
    const GetMSG = (msg)=>{
        console.log(msg)
    }
    return <div>
        <Son GetMSG={GetMSG}></Son>
    </div>
}

export default App;

4.利用状态提升兄弟组件传递

1.通过子传父传递给共同的父组件

2.通过父组件传递给兄弟组件

import {useState} from 'react'

function Son({GetMSG}){
    const test = "test1"
    return <button onClick={()=>GetMSG(test)}>click me !!!!!</button>
}

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

function App(){
    const [name,SetName] = useState("")
    const GetMSG = (msg)=>{
        SetName(msg)
    }
    return <div>
        <Son GetMSG={GetMSG}></Son>
        <Son1 name={name}></Son1>
    </div>
}

export default App;

5.使用context机制实现跨层级组件通信

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

在顶层组件App中通过ctx.provider提供数据

在底层组件使用useContext钩子函数获取消费数据

import {createContext, useContext} from 'react'

//创建
const MsgContext = createContext()

function Son(){
    return <div><Son1/></div>
}

function Son1(){
    const test = useContext(MsgContext)
    return <div>this is Son1,{test}</div>
}

function App(){
    const msg = "test"
    return <div>
        <MsgContext.Provider value={msg}>
            <Son></Son>
        </MsgContext.Provider>
    </div>
}

export default App;

12.useEffect

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

参数已是函数,在函数内放置要执行的操作

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

依赖项不同会有不同的执行表现

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

由渲染本身引起的对接组件外部的操作,副作用操作。比如在useEffect开启了定时器,在下载时清理

清除副作用函数,在组件卸载时会自动执行

useEffect(()=>{
    const timer = setInterval(()=>{
        console.log("test")
    },1000)
    return ()=>{
        //清除副作用
        clearInterval(timer)
    }
},[])

13.自定义hook

use打头的函数,可以实现逻辑的封装和复用

1.声明一个use打头的函数

2.在函数体内封装可复用的逻辑(只要是可复用的逻辑)

使用解构赋值获得use打头的函数返回的对象的值

function gettoggle(){
    const a= 10;
    const b=10;
    return {
        a,
        b
    }
}

function App(){
    const {a,b} = gettoggle()
    return <div></div>
 }

15.ReactHooks

只能在组件中或者其他hook函数(自定义hook函数)中调用

只能在组件的顶层调用,不能嵌套在if for 等其他函数中

使用useEffect优化案例

npm i json-server

npm i axios

import {useState,useRef, useEffect} from 'react'
import axios from 'axios';
const user = {
    uid:1001,
    avatar:"https://www.???.com",
    uname:"richu"
}

function App() {
    const [commentslist,SetCommentslist] = useState([])
    useEffect(()=>{
        async function getlist(){
            const res = await axios.get("http://localhost:3005/list")
            SetCommentslist(res.data)
        }
        getlist()
    },[])
    const [value,SetValue] = useState('')
    const inputRef = useRef(null)
    const [a,SetA] = useState(4);
    const deleteComments = (author)=>{
        if (author === user.uname){
            SetCommentslist(commentslist.filter( item=> item.author !== author))
        }
    }
    
    const test  = () => {
        SetCommentslist([
            ...commentslist
            ,{
            id:'100'+a.toString(),
            name:value,
            author:user.uname
        }])
        SetA(a+1)
        SetValue('')
        inputRef.current.focus()
    }
    return (
        <div className="App">
            <div>{user.uname}</div>
            <input ref={inputRef} value={value} placeholder='你怎么看' onChange={(e)=>SetValue(e.target.value)}></input>
            <button onClick={()=>test()}>发布</button>
            <div>{commentslist.map(item=><div key={item.id}>
                <span>{item.name}</span>
                <button onClick={()=>deleteComments(item.author)}>删除</button>
                </div>)}</div>
        </div>  
    );
} 
export default App;

使用自定义hook封装请求

function useGetlist(){
    const [commentslist,SetCommentslist] = useState([])
    useEffect(()=>{
        async function getlist(){
            const res = await axios.get("http://localhost:3005/list")
            SetCommentslist(res.data)
        }
        getlist()
    },[])
    return {
        commentslist,
        SetCommentslist
    }
}

封装Item

function Item({item,deleteComments}){
    return  (
        <div key={item.id}>
            <span>{item.name}</span>
            <button onClick={()=>deleteComments(item.author)}>删除</button>
        </div>
    )
}

16.Redux

1.Redux

类似pinia(vuex)

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

定义一个reducer对象

使用createStore方法传入reducer函数 生成一个store实例对象

使用subscribe方法订阅数据的变化

dispatch方法提交action对象触发数据变化

getState获取最新的状态数据更新到视图中

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button class="mius">-</button>
    <span class="text">0</span>
    <button class="add">+</button>
    <script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
    <script>
        //定义一个reducer函数
        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
        }
        //使用reducer函数生成store实例
        const store = Redux.createStore(reducer)

        //通过store实例的subscribe订阅数据变化
        store.subscribe(()=>{
            console.log("state+|-")
            document.querySelector(".text").innerText = store.getState().count
        })
        //通过dispatch函数提交状态
        const mbt = document.querySelector(".mius");
        mbt.addEventListener('click',()=>{
            store.dispatch({
                type:"DECREMENT"
            })
        })

        const abt = document.querySelector(".add");
        mbt.addEventListener('click',()=>{
            store.dispatch({
                type:"INCREMENT"
            })
        })
    </script>
</body>
</html>

2.环境配置

redux Toolkit react-redux

1.使用CRA快速创建React项目

npx create-react-app test

2.安装配套工具

npm i @reduxjs/toolkit react-redux

3.启动项目

npm run start

通常集中状态管理的部分会创建一个store目录

子store模块放入modules中

store中的入口文件index.js管理组合modules的所有子模块,并导出store

3.使用redux

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

//解构
const {increment,decrement} = counterStore.actions
//获取reducer
const reducer = counterStore.reducer
//导出需要的模块
export {increment,decrement}
export default reducer
#index.js
import {configureStore} from '@reduxjs/toolkit';

//导入子模块
import counterReducer from './modules/counterStore'
const store  = configureStore({
    reducer:{
        counter:counterReducer
    }
})

export default store
#index.js
import ReactDOM from 'react-dom/client';
import App from './App';
import store from './store';
import { Provider } from 'react-redux';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <Provider  store={store}>
        <App>
          
        </App>
    </Provider>
);
#App.js
import {useSelector} from 'react-redux' 
function App() {
  //结构使用count
  const {count} = useSelector(state=>state.counter)
  return (
    <div>
      <div>{count}</div>
    </div>
  );
}

export default App;

修改store的数据,使用hook函数 useDispatch,它的作用是生产提交action对象的dispatch函数

#App.js
import {useDispatch, useSelector} from 'react-redux' 
//导入actionCreater 
import {increment,decrement} from './store/modules/counterStore'
function App() {
  //结构使用count
  const {count} = useSelector(state=>state.counter)
  const dispatch = useDispatch()
  return (
    <div>
      <button onClick={()=>dispatch(increment())}>+</button>
      <div>{count}</div>
      <button onClick={()=>dispatch(decrement())}>-</button>
    </div>
  );
}

export default App;

4.提交action时传递参数

在调用actionCreater的时候传递参数,参数会被传递到action对象的payload属性上

addToNumber(state,action){
    state.count = action.payload
}

5.Redux与React异步修改

异步修改

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

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

​ 2.1封装异步请求获取数据

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

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

import { createSlice} from '@reduxjs/toolkit'
import axios from 'axios'
const ChannelStore = createSlice({
    name:'channel',
    initialState:{
        channellist:[]
    },
    reducers:{
        setChannels(state,action){
            state.channellist = action.payload
        }
    }
})
const {setChannels} = ChannelStore.actions
//异步请求部分
const fetChannerllist = ()=>{
    return async (dispatch)=>{
        const res = await axios.get('http://geek.itheima.net/v1_0/channels')
        dispatch(setChannels(res.data.data.channels))
    }
}

export {fetChannerllist}

const reducer = ChannelStore.reducer

export default reducer
import {useEffect} from 'react'
import {useDispatch, useSelector} from 'react-redux' 
//导入actionCreater 
import {increment,decrement,addToNumber} from './store/modules/counterStore'
import {fetChannerllist} from './store/modules/channelStore'
function App() {
  //结构使用count
  const {count} = useSelector(state=>state.counter)
  const {channellist} = useSelector(state=>state.channel)
  const dispatch = useDispatch()
  useEffect(()=>{
    dispatch(fetChannerllist())
  },[dispatch])
  return (
    <div>
      <button onClick={()=>dispatch(increment())}>+</button>
      <div>{count}</div>
      <button onClick={()=>dispatch(decrement())}>-</button>
      <button onClick={()=>dispatch(addToNumber(20))}>to 20</button>
      <ul>
        {channellist.map(item=><li key={item.id}>{item.name}</li>)}
      </ul>
    </div>
  );
}

export default App;

17.ReactRouter

路由,一个路径path对应一个Component

const routes = {
	{
		path:"/about",
		componet:about
	}
}

1.创建项目并安装依赖

npx create-react-app react-router-pro

npm i

npm i react-router-dom

npm run start

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

import {createBrowserRouter,RouterProvider} from 'react-router-dom'

const router = createBrowserRouter([
  {
    path:'/login',
    element:<div>login</div>
  },
  {
    path:'/article',
    element:<div>article</div>
  }
])

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <RouterProvider router={router}>
      <App />
    </RouterProvider>

  </React.StrictMode>
);

reportWebVitals();

2.抽象路由模块

在page文件夹新建文件

const Article = ()=>{
    return <div>Article</div>
}

export default Article

router文件夹定义router

import Article from '../page/Article'
import Login from '../page/Login'
import {createBrowserRouter} from 'react-router-dom'

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

export default router

导入router

import router from '../router'; 
import {RouterProvider} from 'react-router-dom'

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <RouterProvider router={router}>
      <App />
    </RouterProvider>
  </React.StrictMode>
);

3.路由导航跳转

1.声明式导航

使用Link标签

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

const Login = ()=>{
    return (<div>
        Login
        <Link to="/article">跳转到文章</Link>
        </div>)
}

export default Login

2.编程式导航

使用useNavigate钩子

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

const Article = ()=>{
    const navigate = useNavigate()
    return (<div>
        Article
        <button onClick={()=>navigate("/login")}>去到文章</button>
        </div>)
}

export default Article

4.导航传参

useSearchParams

1.传递

onClick={()=>navigate(“/login?id=1000&name=tom”)}

2.接收

const [params] = useSearchParams()
const id = params.get('id')
const name = params.get('name')

use

1.占位

{
    path:'/article/:id',
    element:<Article></Article>
}

2.传递

<Link to=”/article/1000”>跳转到文章</Link>

3.接收

const params = useParams()
const id = params.id

5.嵌套路由配置

使用children属性配置路由关系

使用Outlet组件配置二级路由渲染配置

设置默认路由

去掉path,改为index:true

6.404路由配置

当浏览器输入的url的路径在整个路由配置都找不到

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

history模式

createBrowerRouter

hash模式

createHashRouter

18.项目练习–>记账本

1.基本配置

1.Redux状态管理 @reduxjs/toolkit react-redux

2.路由 react-router-dom

3.时间 dayjs

4.class类名处理 classnames

5.移动端组件 antd-mobile

6.请求插件axios

npm i @reduxjs/toolkit react-redux react-router-dom dayjs classnames antd-mobile axios

2.别名路径配置

craco ===>webpack

  • npm i @craco/craco
  • craco.config.js
const path = require('path')

module.exports = {
    webpack:{
        alias:{
            '@':path.resolve(__dirname,'src')
        }
    }
}
"scripts": {
    "start": "craco start",
    "build": "craco build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
}

jsconfig.json ===>vscode

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

3.静态界面的布置

import { useEffect } from 'react'
import { useDispatch } from 'react-redux'
import {Outlet} from 'react-router-dom'
import { getBillList } from '@/store/modules/billStore'
import './demo.css'
import {BillOutline,
    CalculatorOutline,
    AddCircleOutline
} from 'antd-mobile-icons'
import {TabBar } from 'antd-mobile'
const tabs = [
    {
      key: '/month',
      title: '月度账单',
      icon: <BillOutline />,
    },
    {
      key: '/message',
      title: '记账',
      icon: <CalculatorOutline />,
    },
    {
      key: '/year',
      title: '年度账单',
      icon: <AddCircleOutline />,
    },
  ]

const Layout = ()=>{
    const dispatch = useDispatch()
    useEffect(()=>{
        dispatch(getBillList())
    },[dispatch])
    return <div className='layout'>
        <div className='container'>
            <Outlet/>
        </div>
        <div className="footer">
            <TabBar /* onChange={value => setRouteActive(value)} */>
                {tabs.map(item => (
            <TabBar.Item key={item.key} icon={item.icon} title={item.title} />
                ))}
            </TabBar>
        </div>
    </div>
}

export default Layout

4.安装SCSS

npm install sass -D

body{
    div{
        color: blue;
    }
}

5.安装antd

npm i antd –save

6.增加表单校验规则

<Form.Item name="verifycode" rules={[{
    required:true,
    message:"请输入验证码"
}]}>

7.封装axios模块

//axios的封装处理
import axios from "axios";

const request = axios.create({
    baseURL:"",
    timeout:5000
})

request.interceptors.request.use((config)=>{
    return config
},(error)=>{
    return Promise.reject(error)
})

request.interceptors.response.use((response)=>{
    return response.data
},(error)=>{
    return Promise.reject(error)
})

export {request}

8.redux封装token

import {createSlice} from '@reduxjs/toolkit'
import {request} from '@/utils/index'
const userStore = createSlice({
    name:"user",
    initialState:{
        token:''
    },
    reducers:{
        setToken(state,action){
            state.token = action.payload
        }
    }
})

const {setToken} = userStore.actions
const fetchLogin = (loginForm)=>{
    return async (dispatch)=>{
        const res = await request.post('/authorizations',loginForm)
        dispatch(setToken(res.data.token))
    }
}
const userReducer = userStore.reducer

export {setToken,fetchLogin}
export default userReducer

9.封装与token相关的方法,存删取

function setTokens(token){
    localStorage.setItem("token",token)
}

function getToken(){
    return localStorage.getItem("token")
}

function removeToken(){
    localStorage.removeItem("token")
}

export {
    setTokens,
    getToken,
    removeToken
}

10.路由权限控制

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

function AuthRoute({children}){
    const token = getToken()
    if (token){
        return <>{children}</>
    }else{
        return <Navigate to={'/login'} replace/>
    }
}

export {AuthRoute}
import Login from "@/pages/Login";
import Layout from "@/pages/Layout";
import {AuthRoute} from '@/components/AuthRoute'
import {createBrowserRouter} from 'react-router-dom'

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

export default router

11.normalize.css

npm i normalize.css

初始化样式

当token失效后访问后端接口会返回401状态码

12.echarts

npm install echarts

import * as echarts from 'echarts';
import { useEffect, useRef } from 'react';

const BarChart = ({title})=>{
    const chartRef = useRef(null)
    useEffect(()=>{
            //获取渲染图标的dom节点
    const chartDom = chartRef.current
    //初始化图标实例对象
    const myChart = echarts.init(chartDom);
    const option = {
        title:{
            text: title
        },
        xAxis: {
            type: 'category',
            data: ['React','Angular','Vue']
        },
        yAxis: {
            type: 'value'
        },
        series: [
            {
            data: [150 , 120, 200],
            type: 'bar'
            }
        ]
    };
    option && myChart.setOption(option);
    },[])
    return <div ref={chartRef} style={{width:'500px',height:'500px'}}>Home</div>
}

export default BarChart

13.统一管理API

import { request } from "@/utils";

//登录请求
function loginAPI(formData){
    return request({
        url:'/authorizations',
        method:'POST',
        data:formData
    })
}

//获取用户信息
function getProfileAPT(){
    return request({
        url:'/user/profile',
        method:'GET'
    })
}

export {loginAPI,getProfileAPT}

14.富文本

npm i react-quill@2.0.0-beta.2 –legacy-peer-deps

15.使用表单回填数据

const [form] = Form.useForm()
//获取传来的id参数
const [searchparam] = useSearchParams()
const [form] = Form.useForm()
//获取id
const myid = searchparam.get("id")
useEffect(()=>{
    async function getAriticle(){
        const res = await getAriticleByIdAPI(myid)
        form.setFieldValue(res.data)
    }
    getAriticle()
},[myid])

<Form
    labelCol={{ span: 4 }}
    wrapperCol={{ span: 16 }}
    // 注意:此处需要为富文本编辑表示的 content 文章内容设置默认值
    initialValues={{ type: 1 , content: '' }}
    onFinish={onFinish}
    form={form}
>

15.打包运行

npm run build

本地运行

npm i -g serve
serve -s build

16.路由懒加载

1.lazy函数进行动态导入

2.使用react的Suspense组件包裹路由组件

import {lazy} from 'react'

const Home = lazy(()=>import("@/pages/Home"))
const Article = lazy(()=>import("@/pages/Article"))
const Publish = lazy(()=>import("@/pages/Publish"))
children:[
    {
        index:true,
        element:<Suspense fallback={"加载中"}><Home></Home></Suspense>
    },
        {
            path:"/publish",
                element: <Suspense fallback={"加载中"}><Publish></Publish></Suspense>
        },
            {
                path:"/article",
                    element:<Suspense fallback={"加载中"}><Article></Article></Suspense>
            }
                    ]

17.包体积分析

npm i source-map-explorer

"scripts": {
    "start": "craco start",
    "build": "craco build",
    "test": "craco test",
    "eject": "react-scripts eject",
    "analyze":"source-map-explorer build/static/js/*.js"
},

18.CDN配置

craco.config.js

const path = require('path')

module.exports = {
    webpack:{
        alias:{
            '@':path.resolve(__dirname,'src')
        },
        configure: (webpackConfig) => {
            // webpackConfig自动注入的webpack配置对象
            // 可以在这个函数中对它进行详细的自定义配置
            // 只要最后return出去就行
            let cdn = {
            js: [],
            css: []
            }
            // 只有生产环境才配置
            whenProd(() => {
            // key:需要不参与打包的具体的包
            // value: cdn文件中 挂载于全局的变量名称 为了替换之前在开发环境下
            // 通过import 导入的 react / react-dom
            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',
                ],
                css: []
            }
            })
    
            // 都是为了将来配置 htmlWebpackPlugin插件 将来在public/index.html注入
            // cdn资源数组时 准备好的一些现成的资源
            const { isFound, match } = getPlugin(
            webpackConfig,
            pluginByName('HtmlWebpackPlugin')
            )
    
            if (isFound) {
            // 找到了HtmlWebpackPlugin的插件
            match.userOptions.cdn = cdn
            }
    
            return webpackConfig
        }
    }
}

index.html

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

19.useReducer

和useState类似,用来管理相对复杂的状态数据

1.定义一个reducer函数(根据不同的action返回不同的新状态)

2.在组件中调用useReducer,并传入reducer函数和状态的初始值

3.状态发生时通过dispatch分派一个action对象

const [state,dispatch] = useReducer(reducer,0)
import {useReducer} from 'react'

function reducer(state,action){
  switch(action.type){
    case "INC":
      return state+1
    case "DEC":
      return state-1
    default:
      return state
  }
}

function App() {
  const [state,dispatch] = useReducer(reducer,0)
  return (
    <div className="App">
      this is app ---{state}
      <button onClick={()=>dispatch({type:"INC"})}>++</button>
      <button onClick={()=>dispatch({type:"DEC"})}>--</button>
    </div>
  );
}

export default App;

20useMemo

只有在Count1发生变化才执行函数,减少重复的不必要的计算

缓存的是计算的结果

使用:消耗非常大的计算

const [count1,setCount1] = useState(5)
useMemo(()=>{
    return fib(count1)
},[count1])

21.React.memo

经过memo函数包裹生成的缓存组件只有在props发生变化的时候才会重新渲染

默认渲染机制,父组件渲染子组件跟着一起渲染

const memoComponent = memo(function son(){
  return <div>son</div>
})

props 传递一个普通类型 prop改变时组件重新渲染

props 传递一个引用类型 prop改变的时候组件比较引用是否相等,再重新渲染

保证引用稳定 –》使用useMemo 组件渲染的过程中缓存一个值

22.useCallback

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

const handler = useCallback(()=>{
  console.log("123")
},[])

23.React.forwardRef

ref暴露DOM节点给父组件

const Son = forwardRef((prop,ref)=>{
    return <input type="text" ref={ref}/>
})

function App(){
    const SonRef = useRef(null)
    const showREf = ()=>{
        //console.log(SonRef)
        SonRef.current.focus()
    }
    return (
        <div className="App">
            <Son ref={SonRef}/>
            <button onClick={showREf}>focus</button>
        </div>
    )
}

export default App

24.useImperativeHandle

向父组件暴露子组件的方法

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

/* function Son(){
    return <input type="text"/>
} */

/* const Son = forwardRef((prop,ref)=>{
    return <input type="text" ref={ref}/>
})

function App(){
    const SonRef = useRef(null)
    const showREf = ()=>{
        SonRef.current.focus()
    }
    return (
        <div className="App">
            <Son ref={SonRef}/>
            <button onClick={showREf}>focus</button>
        </div>
    )
}

export default App */

const Son = forwardRef((prop,ref)=>{
    const inputRef = useRef(null)
    const inputfocus = ()=>{
        inputRef.current.focus()
    }
    useImperativeHandle(ref,()=>{
        return {
            inputfocus
        }
    })
    return <input type="text" ref={inputRef}/>
})

function App(){
    const SonRef = useRef(null)
    const handlerclick = ()=>{
        SonRef.current.inputfocus()
    }
    return (
        <div className="App">
            <Son ref={SonRef}/>
            <button onClick={handlerclick}>focus</button>
        </div>
    )
}

export default App

25类组件API

1.定义类属性state定义状态数据

2.通过setState方法来修改状态数据

3.通过reducer来写render来写UI模版

import { Component } from "react";

class Counter extends Component{
    state = {
        count : 0
    }
    setCount = ()=>{
        this.setState({
            count:this.state.count + 1
        })
    }

    render(){
        return <button onClick={this.setCount}>{this.state.count}</button>
    }
}

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

export default App

26.类组件的生命周期

componentDidMount:组件挂载完毕自动执行 异步数据获取

componentDidUpdate:组件更新

componentWillUnmount:组件卸载时自动执行 清理副作用

27.类组件中父子类传递信息

import {Component} from 'react'

class Son extends Component{
    state={
        msg : "son"
    }
    render(){
        //使用属性  使用this.props.属性名
        return <div>
            son  --{this.props.msg}
            <button onClick={this.props.onGet(this.state.msg)}>传递</button>
        </div>
    }
}

class Parent extends Component{
    state = {
        msg : "test"
    }
    onGet = (Sonmsg)=>{
        console.log(Sonmsg)
    }
    render() {
        return (<div>father
            <Son msg={this.state.msg} onGet={this.onGet}/>
        </div>   )
    }
}

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

export default App

27极简的状态管理工具zustand

1.zustand的基本使用

1.创建store 状态数据操作方法

2.component消费数据和方法

import {create} from 'zustand'

const useStore = create((set)=>{
    return {
        count:1,
        inc:()=>{
            //需要用到老数据传递函数
            set((state)=>({count:state.count + 1}))
            //当不需要用到老对象时可以直接传递参数
            set({count:100})
        }
    }
})

function App(){
    const {count,inc} = useStore()
    return (
        <div>
            <button onClick={inc}>{count}</button>
        </div>
    )
}

export default App

2.zustand异步支持

//异步函数逻辑
fetchList:async ()=>{
    const res = await fetch("http://geek.itheima.net/v1_0/channels")
    const jsonRes = await res.json()
    set({channelList:jsonRes.data.channels})
}

使用

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

3.zustand切片模式

当单个store比较大时,可以采用切片模式进行拆分

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

const countStore = (set)=>{
    return {
        count:1,
        inc:()=>{
            //需要用到老数据传递函数
            set((state)=>({count:state.count + 1}))
            //当不需要用到老对象时可以直接传递参数
        },
    }
}

const channelStore = (set)=>{
    return {
        channelList:[],
        fetchList: async ()=>{
        const res = await fetch("http://geek.itheima.net/v1_0/channels")
        const jsonRes = await res.json()
        set({channelList:jsonRes.data.channels})
    }
    }
}

const useStore = create((...a)=>{//...a固定写法
    return {
        ...countStore(...a),
        ...channelStore(...a)
    }
})

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

export default App

28.React+TypeScript

1.配置安装

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

npm i

2.useState自动推导

const [value,toggle] = useState(false)
const change = ()=>{
    toggle(100)
}
//类型“100”的参数不能赋给类型“SetStateAction<boolean>”的参数

3.useState泛型参数

type Person{
  name:String,
  age:Number
}

function App() {
 const [user,Setuser] = useState<Person>()
  return (  
    <>
      <div>
        this is app
      </div>
    </>
  )
}

限制useState函数的参数为Person | ()=>Person

限制setuser函数的参数必须满足类型为Person | ()=>User | undefined

Person状态数据具备User类型相关的类型提示

4.useState初始值为null

限制useState的参数可以是Person | null

限制setUser的参数可以是Person | null

const [user,Setuser] = useState<Person | null>(null)

5.props类型

type Props={
  className:String
}

function Son(props:Props){
  const {className} = props
  console.log(className)
  return (<div>test</div>)
}

function App() {
  return (  
    <>
      <div>
        this is app
        <Son className={"test"}/>
      </div>
    </>
  )
}

6.children

children是特殊的props,支持不同的类型数据的传入,需要通过一个内置的ReactNode类型来注解

type Props={
  className:String
  children:React.ReactNode
}

function Son(props:Props){
  const {className,children} = props
  console.log(className)
  return (<div>{children}</div>)
}

function App() {
  return (  
    <>
      <div>
        this is app
        <Son className={"test"}>this is son</Son>
      </div>
    </>
  )
}

7.为事件prop添加类型

import { useState } from "react"

/* type Person{
  name:String,
  age:Number
} */

type Props={
  onGetMsg:(msg:String)=>void
}

function Son(props:Props){
    const {onGetMsg} = props
    const msg = "hello"
  return <button onClick={()=>onGetMsg(msg)}>click me!!!</button>
}

function App() {
    const onGetMsg = (msg:String)=>{
        console.log(msg)
    }
  return (  
    <>
      <div>
        this is app
        <Son onGetMsg={onGetMsg}/>
      </div>
    </>
  )
}

export default App

8.useRef

import { useEffect, useRef, useState } from "react"

function App() {
    const domRef = useRef<HTMLInputElement>(null)
    useEffect(()=>{
        //可选链
        domRef.current?.focus()
    },[domRef])
    return (  
        <>
        <div>
            this is app
            <input ref={domRef} placeholder="input"></input>
        </div>
        </>
    )
}

export default App

当成稳定引用的存储器使用

import { useEffect, useRef, useState } from "react"

function App() {
    const domRef = useRef<HTMLInputElement>(null)
    const timeRef = useRef<number | undefined>(undefined)
    useEffect(()=>{
        //可选链
        domRef.current?.focus()
        timeRef.current= setInterval(() => {
            console.log("1");
        }, 1000);

        return ()=>clearTimeout(timeRef.current)
    },[domRef])
    return (  
        <>
        <div>
            this is app
            <input ref={domRef} placeholder="input"></input>
        </div>
        </>
    )
}

export default App

9.路径别名配置

让vite做路径解析

让VScode做智能提示

//vite.config.ts

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

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

npm i @types/node -D

vscode智能提示

//tsconfig.app.json
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,
    "baseUrl": ".",
    "paths": {
      "@/*":[
        "src/*"
      ]
    },
    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "isolatedModules": true,
    "moduleDetection": "force",
    "noEmit": true,
    "jsx": "react-jsx",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["src"]
}

API模块封装

通用泛型

type ResType<T> = {
    message:String,
    data:T
}

具体泛型

type User = {
    id:number,
    name:String
} 

type UserList = {
    users:User[]
}

使用泛型

function fetchUserList(){
    return http.request<ResType<UserList>>({
        url:"/channels"
    })
}

反文件

export type UserList = {
    users:User[]
}

export type ResType<T> = {
    message:String,
    data:T
}

type User = {
    id:number,
    name:String
}
import {http} from '@/utils'
import type {ResType,UserList} from '@/apis/type/user'


function fetchUserList(){
    return http.request<ResType<UserList>>({
        url:"/channels"
    })
}

export {fetchUserList}

使用hook优化代码,将代码逻辑抽离

import { useEffect, useState } from 'react';
import type {User} from '@/apis/type/user'
import {fetchUserList} from '@/apis/user'

const useTab = ()=>{
    const [userList,setUserList] = useState<User[]>([])
useEffect(()=>{
    const getUsers = async ()=>{
        try{
            const res = await fetchUserList()
            setUserList(res.data.data.channels)
        }catch(error){
        throw new Error("error")
        }
        }
        getUsers()
    },[])
    return {
        userList
    }
}

export {useTab}