React18 学习笔记

792 阅读12分钟

前引:写这篇文章的目的是为了让自己回头看的时候能快速捡起当初的一些知识,同时也分享出来共大家学习讨论

一、React概述

1.介绍

把用户界面抽象成一个个组件,按需组合成页面,采用虚拟dom和数据驱动(组件的嵌套堆叠)

2.与其他框架性语言的对比

image.png

二、yarn使用(以下使用yarn作为包管理工具)

1.安装

npm i yarn -g

2.初始化一个新项目

yarn init

3.添加依赖包

yarn add xx

4.升级依赖包

yarn add xx

5.移除依赖包

yarn remove xx

6.安装packjson的项目依赖

yarn install

7.安装到全局

yarn global add/remove xx

8. 镜像相关

查询当前镜像

yarn config get registry

设置为淘宝镜像

yarn config set registry registry.npm.taobao.org/

设置为官方镜像

yarn config set registry registry.yarnpkg.com

三、环境搭建

1.创建react项目

yarn create react-app 目录

  • 三个核心包
    react(解析组件,核心语法,jsx演示)
    react-dom(将虚拟dom转换成真实dom)
    react-scripts
  • 注意
    react脚手架可以在线调试,不用安装在本地(安装到内存中不是磁盘中)
    目录名不能是中文和全大写

2.开发react

yarn start

3.打包react

yarn build

生成build目录

4.配置react

yarn eject

生成config,scripts目录
  • 注意
    react的配置文件是封装在node_modules里
    vue是可以通过外部写个vue.config.js配置文件,合进node_modules中,而react 不行
    //修改端口
    script/start.js const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000; 
    //去除eslint 警告
    config/webpack.config.js //注释关于new ESLintPlugin (react1.x)
  • 错误处理

yarn eject

报git错误时: git init -> git add . -> git commit -m 'init' -> yarn eject

四、jsx

1.理解

就是一种数据类型

2. 语法

var b= <strong>强壮</strong>

3.语法要求

- 标签要闭合

- 元素必须要有一个顶层元素

- 变量大驼峰代表组件,小写对应jsx

- 属性,小驼峰命名 `<xx tabIndex="2">`

- 有换行时要用括号 
var a = (

<ul>

<li></li>

</ul>

)

4.用例

image.png

image.png

要是不用jsx如何创建虚拟dom?

用createElement这个api

image.png

五、渲染页面

17版本

import ReactDom from 'react-dom';

ReactDom.render(jsx,插入点,回调函数)

插入点要获取自己获取dom

18版本

import ReactDOM from 'react-dom/client';

const root = ReactDOM.createRoot(插入点);

root.render(jsx)

思考

1.感觉react脚手架也是下到本地的?

只是临时下到内存里面,不会安装到硬盘上

2.jsx和ts有什么关系?

jsx是一种新的数据类型,而ts是js的超集

3.react的结构和行为揉在一起?

就是html和js都在.js文件中

六、资源限制

  • 本地资源导入(import) 不可以导入src之外的包
  • 相对 路径以文件所在位置为基准顶层到src,绝对路径 的根是 public目录
  • 前景图片, 相对 和 绝对路径 都指向了 public目录(因为是数据)

七、组件

1.与vue的对比

vue .vue>template/script/style

react .js>jsx(很多个jsx变成一个component)/js

其实jsx和js性质一样,只是用来做命名区分

2.创建组件

历史方式

api -> class -> function 语法

  • const 组件名=(props)=>(jsx)
  • const 组件名=props=>jsx
  • const 组件名=(props)=>{ //业务 return jsx | number | string | null } function 组件名(props){}

image.png

调用组件

<组件名 />

组件嵌套

function 组件名1(props){ return ( .. <组件名2/> .. ) } 
function 组件名2(props){ return ( .. <组件名3/> ) }

八、片段

1.目的

去除顶层元素 引入

import {Fragment} from 'react'; 从react包里解出来的是大驼峰的东西就是组件

2.使用

用<Fragment></Fragment>

  • 简写:<></> (且不用引入)
  • 只接受key和children属性

九、props

1.目的

父子间传值

2.使用

传递属性

<组件名 属性名= 属性名2=值2 .. />

使用属性

function 组件(props){ return (
{props.属性名}
) }

3.注意

  • jsx中接收js用{}来
  • 传递的是对象时,不能直接访问对象,只能访问其中的属性

image.png

十、事件

1.使用

<jsx onClick={} />         // 事件命名采用小驼峰的方式
<jsx onClick={()=>{}} />  // 调用函数需要接参的形式

image.png

2.事件对象

函数(ev)

  • 返回的虚拟dom
  • 时间对象会隐式传递到末尾参数

3.阻止冒泡

ev.stopPropagation()

4.默认行为

ev.preventDefault()

5.事件传参

onClick={ev=>函数(12,ev)}

image.png

6.父子间调用

父传子

父:function sayhi(){}
<子 show={sayhi} />
子:const 子 = (props)=>{
    props.show()
}

子传父

父:function sent(num){}
<子 show={sent} />
子:const 子 = (props)=>{
    props.show(88)
}

子组件可以调用父组件传递来函数方法来直接调用父函数或通过调用父函数给父函数传值的形式完成子向父传值的过程

思考

为啥传递数字的时候要用{}包裹,字符串不用?

一种简写方式

十一、组件状态(数据)

1.理解

相当于vue中的数据

2.使用

引入

import { useState } from 'react'

定义

const [状态变量,状态方法] = useState(状态属性的初始值)
const [count,setCount] = useState(0);

使用状态

{状态变量}

修改状态

状态方法(新值)
状态方法(当前值 => 新值)

用例

image.png

注意

  • use系列的方法不能被嵌套,只能放在组件函数的最顶层
  • 可以自由命名,状态变量可以不只一个state变量
  • null,undefined,boolean,空字符都不渲染,数组会取元素再拼接成字符串
  • vue里是null和undefined不渲染,其他会给你转成字符
  • useState里的数据是immutable(不可赋值数据),比如刷新之后数据又变为原来初始化的

十二、列表渲染

1.理解

用js语法构建jsx

2.语法

<ul>
    {
        arr.map((v,i)=>{
            return <li key={i}></li>
        })
    }
</ul>

嵌套循环

image.png

十三、条件渲染

1.语法

状态变量 ? jsx1: jsx2
状态变量 && jsx

2.注意

当拿到的数据层级比较深,前面已经是undefined再往下调用会报错,可以用ts的链式关系符来解决A?B

思考

为啥流程语句if不能写在{}中?

其中放的是表达式

十四、ref

1.背景

vue和react都是数据驱动,为了提高性能在数据多次修改时,只有最后一次才会真的去修改

2.场景

需要抓取dom元素与第三方dom库集成,触发命令式动画,管理焦点,文本选择或媒体播放,图形可视化操作

3.用法

createRef

组件每次渲染都会重新创建

// 引入createRef
import {createRef} from 'react';

// 初始化
const firstRef = createRef();

// 使用
<jsx ref={firstRef} />

image.png

useRef

组件每次渲染都指向同一个ref,useRef指向的是一个引用

// 引入useRef
import {useRef} from 'react';

// 初始化
const firstRef = useRef();

// 使用
<jsx ref={firstRef} />

image.png

callback

组件使用到的时候都会重新创建,直接指向dom

// 初始化
let firstRef = null;

// 使用
<jsx ref={ (el)=>{firstRef=el} } />

image.png

转发ref

父组件定一个ref套到子组件里的dom上,在父组件内对其进行操作

// 父
const r1 = useRef();
<子 ref={r1} />

// 子
import {forwardRef} from 'react';
const 子 = (props,ref)=>{
return <div ref={ref}></div>
}
export default forwardRef(子);

4.useRef作用

  • useRef用于返回一个可变的ref对象。这个ref对象的current属性被初始化为useRef传入的参数initialValue
  • useRef返回的ref对象在整个生命周期中保持不变。(意思是这个ref对象的地址一直不会变)
  • ref对象变化不会触发视图更新。(但是当有state改变时,ref对象的变化也会显示在视图上)
  • 获取的DOM实例将会储存在current属性。(current属性指向DOM实例)

注意

createRef和useRef都是执行绑定ref元素的引用,callback指向dom元素本身

十五、表单元素的受控元素

1.受控元素

react给jsx绑定上的元素是只读的不可以改变
<input type="text" value={value} />

2.非受控元素

可以用defaultValue来让表单绑定的元素可写,但是实际的数据不会改变
<input type="text" defaultValue={value} />

3.模拟双向绑定

<input type="text" value={value} onChange={ev=>{
setValue(ev.target.value);
}}/>

思考

不能用模板字符串?

${引用数据类型}会转成[object,object]

image.png

ref转发只能传一个ref?

可以将多个r1,r2,r3放到一个对象中,这样就传过去就可以通过 对象.r1 来绑定

为啥不能对list直接操作再用setList,得用个中间变量来接收值来重新赋值?

数据都是不可变数据,不能对其直接修改,方法赋值也只是在其上叠了一层数据

{list}是响应式的在别处修改了会响应式变化?

数据发生变化函数组件重新执行

十六、Portals

1.概述

结构脱离当前组件,但是其事件行为仍存在与之前的react数中,且与dom数中的位置无关,html结构是脱离的

2.场景

对话框、悬浮卡、提示框

3.语法

// 引入
import {createPortal} from 'react-dom';

// 使用
createPortal(jsx, 要注入的dom节点);

image.png

十七、css样式

1.两种引入方式

<link rel="stylesheet" href="./css/base.css"
一般用于引入第三方样式,不会进行压缩
import "./assets/css/base.css"
引入公共样式,css文件放在src下会进行优化

2.使用

<jsx className="类名 类名2" className={返回字符}
style的属性值,可以不给单位,默认px,子属性小驼峰

<jsx style={{key:value,key:value}}

3.选择器冲突解决方案

命名空间

*B__E--M block 区域块 element 元素 midiler 描述|状态
// B__E--M
// b-b-b__e-e-e--m-m-m

模块化

// 引入模块化css
import 变量 from './css/xx.module.css'

// 使用
<jsx className={变量.类名|id}
<jsx className={`${变量.xx} ${变量.xx}`}

思考

数据每次变化,函数组件都重新执行一次,这样不耗费性能吗?

有类似vue的diff算法,只是局部更新

十八、scss

1.安装

node-sass sass-loader,需要注意的react18自带sass-loader版本要升到最高,再去安装node-sass就可以成功

2.使用

image.png

3.引入scss全局变量

直接引入

// xx.scss
@import './全局.scss'

webpack配置

1. 安装插件sass-resources-loader

2. 配置修改webpack.config.js
{
test:sassRegex,
...
use: [
{loader:'style-loader'},
{loader:'css-loader'},
{loader:'sass-loader'},
{
loader: 'sass-resources-loader',
options:{
resources:'./src/xx/全局主题.scss'
            }
        }
    ]
}

注意

scss主要使用的东西 -> 变量,包含,复选器,函数,全局选择器

十九、less

1.安装

less less-loader sass-resources-loader

2.webpack配置

react18默认没有less的任何配置,我们可以拷贝scss的规则去修改

image.png

3.使用

image.png

二十、数据可视化图表

1.概述

一般使用canvas或svg来进行绘制

2.第三方组件库

echarts 安装 yarn add echarts --save

引入 import * as echarts from 'echarts'

使用

//实例化
let instance=echarts.init(dom元素);

//渲染
instance.setOption(数据)

//API:
instance.showLoading()/hideLoading()/ on('事件名',方法)

//事件:
instance.on('click', function (params) {
// 控制台打印数据的名称
console.log(params.name);
});

百度地图 引入库 <script type="text/javascript" src="https://api.map.baidu.com/api?v=1.0&type=webgl&ak=UBMP6QMGFzoEIgb45h5RO0XD6nZcHvYT"></script>

使用

// 绑定一个dom元素
<div ref={r1} style={{height:'500px'}}></div>
// 根据api文档绘制地图
const drawMap = ()=>{
map = new window.BMapGL.Map(r1.current); // 创建Map实例
point = new window.BMapGL.Point(103.9349414, 30.7562183); // 坐标点
map.centerAndZoom(point, 15); // 初始化地图,设置中心点坐标和地图级别
map.enableScrollWheelZoom(true); // 开启鼠标滚轮缩放
createMypos()
}
// 查看demo实例定义使用不同的api方法

ant design 安装 yarn add antd --save

样式引入 @import '~antd/dist/antd.css';

依照按需引入原则

image.png

使用 image.png

国际化

1. 原因
组件原本的内置文案很可能是英文的要将其转为中文的

2. 使用
// 引入组件
import { ConfigProvider } from 'antd';

// 引入语言包
import zhCN from 'antd/lib/locale/zh_CN';

// v4版本 深度优化
import moment from 'moment';
import 'moment/locale/zh-cn';
moment.locale('zh-cn');

const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(
<ConfigProvider locale={zhCN}>
<App />
</ConfigProvider>
);

image.png

表单组件

1. 不受原生的双向绑定的影响,提供通过给Form绑定个form,再通过submit执行onFinish方法将每个表单内的值获取到,通过setFieldsValue来重新设置表单内的值

2. 表单验证
① 引入from(onFinish是对成功时事件调用)
② 将添加按钮上的自定义事件去掉,换成htmlType="submit",通过自身的submit进行校验成功或失败
③ 引入<Form.Item></Form.Item>进行规则限定

antd mobile 安装 yarn add antd-mobile --save

使用组件 import { DatePickerView } from 'antd-mobile'

主题 :root:root { --adm-color-primary: #a062d4;}

国际化

import enUS from 'antd-mobile/es/locales/en-US'
import { ConfigProvider } from 'antd-mobile';
<ConfigProvider locale={enUS}>
<App />
</ConfigProvider>

思考

如何理解cdn?

内容分发网络,简单理解就是设置在服务器与用户间更近用户的服务器,加速数据访问

二十一、children

1.概述

类似与vue的插槽,通常封装一部分能力时,做成一个组件,把需要共享此功能的部分嵌套进去,如:layout组件,或者一些普通的通用组件 image.png

思考

直接在组件中插入其他组件和通过props.children的方式有什么区别?

后者更灵活,将结构感暴露出来使结构感更清晰

二十二、生命周期

1.背景

  • 每当React更新之后,就会触发useEffect,在第一次render和每次update后触发,不用再去考虑"挂载"还是"更新"
  • React保证了每次运行effect的同时,DOM都已经更新完毕

2.种类

单挂载周期

useEffect(()=>{
    console.log('组件挂载完毕')
},[])

函数+数组

挂载||更新周期

useEffect(()=>{
    console.log('组件挂载||更新完毕')
})

函数

卸载周期

useEffect(()=>{
    return ()=>{
        console.log('组件卸载完毕')
    }
},[])

return+数组项可有可无

对单数据进行挂载||更新监听

useEffect(()=>{
    console.log('bl更新了')
},[bl])

函数+数组

注意

  • vue是view上dom数据变化了触发updated,react是vdom数据变化就触发
  • effect的触发顺序就是你声明的顺序
  • 正常情况下,子更新不会触发父更新,父更新会触发子更新

二十三、数据请求

1.种类

fetch

概述

js原生的api,是promise的语法糖

语法

const params = new URLSearchParams();
params.append("username", "aaa");
params.append("password", "aa123");

fetch("/api/reg", {
method: "post",
headers: { "Content-type": "application/x-www-form-urlencoded" },
body: "username=aa&password=aa123",
// body: params,
})
.then((res) => res.json()) //解流
.then((data) => console.log(data));

注意

  • 默认是get请求
  • body里的数据是流式传输,需要截流才能获取里面的数据
  • async+await 用法
  • body数据为字符时,需要携带请求头
res.ok : true/false 成功/失败
res.status: 状态码
res.body : 数据 数据流(stream)
res.text() :转换 文本(string),过程异步,return res.text()
res.json() :转对象

axios

umi-request

概述

网络请求库,基于fetch封装,兼具fetch和axios的特点

安装 npm install --save umi-request

引入 import request from 'umi-request'

语法 request.get('url'[, options]).then(res => { console.log(res); });

对比

image.png

2.客户端代理

// src/setupProxy.js

//verion < 1.0
const proxy = require('http-proxy-middleware'); //需要安装中间件
module.exports = function(app) {
app.use(
proxy("/api", {
target: 'https://uncle9.top',
changeOrigin: true
})
);
app.use(
proxy("/v2", {
target: "https://api.douban.com",
changeOrigin: true
})
);
};
//组件: /api/xx ... | /v2/...

//verion > 1.0
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use('/api', createProxyMiddleware({
target: 'http://localhost:3001',
changeOrigin: true,
}));
app.use('/api2', createProxyMiddleware({
target: 'http://vareyoung.top',
changeOrigin: true,
pathRewrite: { //路径替换
'^/api2': '/api', // axios 访问/api2 == target + /api
}
}));
};

二十四、路由(v6)

1.概述

React Router被拆分成三个包:
- react-router(操作其他程序源)
- react-router-dom(操作浏览器端的)
- react-router-native(操作原生端的)

react-router提供核心的路由组件与函数,其余两个则提供运行环境(即浏览器和react-native)所需的特定组件

2.安装

yarn add react-router-dom --save

3.提供组件

image.png

4.组件树结构

image.png

image.png

5.组件属性

BrowserRouter

image.png

Route

image.png

Link

image.png

NavLink

image.png

image.png

Navigate

image.png

6.参数数据

传递

image.png

接收

image.png

import { useParams,useSearchParams,useLocation } from 'react-router-dom'

  • searchParams.get('search的key')
  • location.pathname/hash/key
  • params.动态路由名变量

7.编程式跳转

import { useNavigate } from 'react-router-dom';
function 组件(){
    const navigate = useNavigate();
    navigate("/login");
    navigate("/reg",{replace: true}); // 清除历史记录
    navigate({pathname:"/goods/5",search: "a=6&b=8"},{replace: true});
    navigate(-1) // 回退
}

8.路由守卫

独享守卫

1.在集中式的路由管理中使用组件嵌套的方式

image.png

2.对于守卫路由组件,通过条件加Navigate组件实现路由守卫,通过useLocation传递跳转前被守卫路由的信息

image.png

3.在跳转后路由页面通过useLocation来获取跳转前路由相关信息,以及其他路由信息

image.png

全局守卫

1.在集中式路由管理的Routes上层嵌套全局路由守卫组件(因为Routes下只能放Route所有全局路由守卫只能放在Routes上层)

image.png

2.在全局守卫中进行条件设置

image.png

注意:可以使用先返回null页面,等待异步请求回来的数据判断完毕后再进行路由跳转业务

9.路由懒加载(异步组件)

概述

使用lazy和Suspense组件

使用

1. 引入
import {lazy,Suspense} from "react"

2. 使用lazy对路由页面进行嵌套
// 原型import User from '../pages/User'
const User = lazy(()=>import("../pages/User"))

3. 使用SuspenseRoutes进行包裹
<Suspense fallback={<>...loading组件</>}>
<Routes>
<Route path="/" element={<App />}>
...
</Route>
</Routes>
</Suspense>

10.路由对象数据化

概述

动态获取路由数据信息,再用useRoutes钩子将路由数据转化为路由对象

使用

1. 引入
import { BrowserRouter,Navigate,useRoutes } from 'react-router-dom'
import { lazy,Suspense} from 'react'

2. 异步化组件
const Login = lazy(()=>import('../pages/Login'))

3. 获取动态路由数据
let routes = [
{
path: '/',
element: <App />,
children: [
{ path: 'home', element: <Home /> },
{
path: 'goods',
element: <Goods />,
children: [
{ path: ':id', element: <Detail /> },
{ index: true, element: <Navigate to="1" /> },
],
},
{ path: 'user', element: <User /> },
{ index: true, element: <Navigate to="home" /> },
{ path: '*', element: <NoPage /> },
],
},
{ path: '/login', element: <Login /> },
{ path: '/Reg', element: <Reg /> },
];

4. 转化为路由对象
const Routes = () => useRoutes(routes)

5. 插入组件
<BrowserRouter>
<Suspense>
<Routes />
</Suspense>
</BrowserRouter>

注意

默认页和重定向的区别

image.png


持续更新中...