函数组件和类组件的区别
1、函数组件没有生命周期
2、函数组件没有this
3、函数组件通过hook来完成各种操作
- 函数组件本身的函数体相当于render函数
- props在函数的第一个参数接受
类组件的生命周期
ps:函数组件没有生命周期

相比vue少一个create阶段

挂载:

更新:


在生命周期中自定义,例:如果没有发生改变,不执行后续生命周期

重点生命周期:
- render
通过render函数的执行来决定组件渲染什么内容,所以无论更新还是初次挂载都必须执行render
- componentDidMount
组件挂载完成,一般用来做一些页面初始化操作,比如初始请求echart绘制等,也就是vue的mounted里能做的事
- shouldComponentUpdate
更新阶段调用,如果return false则不会执行render函数继续更新从而达到阻止更新的效果,一般用来做性能优化。
- componentDidUpdate
更新完成,等同于vue的updated
- componentWillUnmount
组件即将卸载,通常做一些全局事件的监听的卸载,定时器,计时器的卸载,来优化性能
官网推荐函数组件
官方主推函数组件,类组件复杂冗余
JXS
js和html写在一起、模板语法
JSX只能返回一个根元素
可以写空标签<></>
javascript
function MyButton() {
return (
<button>I'm a button</button>
);
}
插值
单括号{}
插值可以使用的位置:
- 标签内容
- 标签属性
条件渲染
可以直接在js代码里写html,不用模板字符串做拼接

列表渲染
<Fragment></Fragment>循环时若存在多个根标签,可使用react提供的Fragment标签包裹

事件处理

设置class和style

设置class
- 写在css文件里
- 给标签设置class时要改名为className ,因为类选择器class与js里的class重名
- 实现vue中的scope 样式只针对组件生效:
css文件命名规则:组件名-moudle.css
使用css className:文件.className

- 可以使用classnames库:
更方便帮助我们操作类名的库-classnames
本质是帮助我们生成一个字符串
javascript
import classnames from "classnames";
...
<div
className={
classnames({
son:true,
son1:false
})
}
>
</div>
模块化css文件需要引入classnames的bind文件夹及使用 classnames.bind方法

设置style 可写对象字面量(键值对)

jsx支持在标签中使用{...obj}的方式添加多属性:

组件通信 与插槽
父组件向子组件传参
React组件的props

模拟Vue组件插槽 - 将JSX作为Props传递
注:props只读,不做修改
默认插槽:插槽中的值作为props中的children传递给子组件

具名插槽:

props的类型验证和默认值

- 自定义验证

- 默认值
javascript
Son.defaultProps = {
mes: 'I am a default'
}
- proptypes库 react的props验证
npm安装
- 使用useState和useEffect模拟默认props(推荐方式)
javascript
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
function MyComponent({ name }) {
const [internalName, setInternalName] = useState(name);
useEffect(() => {
if (!name) {
setInternalName('Default Name'); // 设置默认值
}
}, [name]); // 依赖项列表包括name,以便在name改变时重新设置默认值
return <div>{internalName}</div>;
}
MyComponent.propTypes = {
name: PropTypes.string, // 注意这里不需要isRequired,因为我们通过useEffect控制了默认值
};
export default MyComponent;
摘要
要传递 props,请将它们添加到 JSX,就像使用 HTML 属性一样。
- 要读取 props,请使用 function Avatar({ person, size }) 解构语法。
- 你可以指定一个默认值,如 size = 100,用于缺少值或值为 undefined 的 props 。
- 你可以使用 <Avatar {...props} /> JSX 展开语法转发所有 props,但不要过度使用它!
- 像 <Card><Avatar /></Card> 这样的嵌套 JSX,将被视为 Card 组件的 children prop。
- Props 是只读的时间快照:每次渲染都会收到新版本的 props。
- 你不能改变 props。当你需要交互性时,你可以设置 state。
子组件向父组件传值:
父组件:

子组件:


同级组件传值
父组件中转
多级组件传值
使用Context进行多级组件传值
类似于vue的provider和injected,用于嵌套很深的爷孙组件之间传值。
注意事项:
1,子组件使用父组件创建的context对象,不能自己创建
2、Context可以嵌套,但是不推荐。
父组件:
只能传递value这一个props,多个值可以传递对象
javascript
export const Context1 = React.createContext()
javascript
<Context1.Provider value={'context数据'}>
<Son></Son>
</Context1.Provider>
孙组件:
使用import从父组件中引入{Context1}
javascript
function GrandSon() {
return <div>
<Context1.Consumer>
{
(value) => {
return <div>{value}</div>
}
}
</Context1.Consumer>
</div>
}
ref
用于获取真实dom
和vue中的ref-个道理。
注意事项
1,ref必须在挂载后才能获取,通常在componentDidMount
2,ref获取组件(类组件),不能获取函数组件
reducer
实现vue中v-if v-for
v-if 条件渲染

react中{}里为false null underfind不会渲染
{条件&&html}
条件为true返回html,条件为false返回false 不渲染
v-for 列表循环


表单绑定

双向绑定
javascript
function App() {
const [inputValue,setInputValue] = useState("")
return (
<div className="App">
{/* 输入框 */}
<div>
<input
value={inputValue}
onInput={(e) => {
setInputValue(e.target.value)
}}
/>
</div>
{/* 输入内容 */}
<div>{inputValue}</div>
</div>
);
}
函数组件 的hook
官方主推函数组件,类组件复杂冗余
useState
状态处理(可变的变量)
Vue会响应式处理,React没有这种机制,需要使用useState
因为:每个变量都默认具备状态更新机制会使页面负担大,所以按需更新
javascript
function App(){
const[content,setContent]= useState('标签的默认内容')
function handleclick(){
setContent('新内容')
}
return(
<>
<div>{content}</div>
<button onClick={handleClick}>按钮</button>
</>
)
}
对象形式的状态
javascript
function App(){
const [data, setData]= useState({
title:'默认标题",
content:'默认内容
})
function handleclick(){
setData({
...data,
title:'新标题
})
}
return(
<>
<div title={data.title}>{data.content}</div>
<button onClick={handleClick}>按钮</button>
</>
)
}
数组形式的状态:和对象类似,需要写全
useEffect
定义副作用
生命周期模拟及数据监听
- 不传第二个参数=componentDidMount(组件挂载,vue的mounted)和componentDidUpdate(更新完成,vue的updated)
- 第二个参数传空数组=componentDidMount(组件挂载,vue的mounted),更新完成后不会再调用
- 第二个参数数组里放某个数据=watch监听(vue watch默认一开始不会执行,但useEffect开始就会执行)
useMemo
让一段计算在开始运行一次,后续只有依赖的数据发生变化时才重新运算作用:
1,起类似于vue的一个计算属性的效果。
2,缓存一个数据,让其不会重新创建。
第二个参数的作用--生命周期模拟及数据监听 和useEffect类似
javascript
let all =useMemo(()=>{
console.log("recount")
let _al1 =0;
arr.forEach((item)=>{
_all += item;
})
return all
},[arr])
useCallbac k
缓存方法,让方法不会每次更新都重新创建
第二个参数的作用--生命周期模拟及数据监听 和useEffect类似
useRef
函数组件中使用ref
javascript
let dom1=useRef();
javascript
<div ref={dom1}> 哈喽</div>
useContext
更方便的解析context和provider的数据
javascript
let value= useContext(Context1)
Hook还有很多,这里只讲了几个常用的,我们也可以自定义hook。
Hook只能用于函数组件,用在别的地方会报错
高阶组件
逻辑复用

使用例子:
1,提供复用的数据和方法,给到组件的props,你可以把很多页面都有的一些逻辑操作提取出来混入。
2,提供生命周期操作,比如写一个高阶组件形式的PureComponent
性能问题和优化
React最大的一个性能问题就是-React的某个组件的更新会连带着,他的子组件一起更新。
所以我们需要解决这个问题!
1、源码层面上尽量弥补这个问题
- 让子组件只做合理的更新
React的时间切片
Vue有依赖收集,做到了最小的更新范围,而React没有做这个事情。所以React要更新,就会有很大的diff算法比对和计算工作。
这大的更新量,虚拟dom比对和计算会花很大时间,这样可能会阻塞住浏览器的工作,导致页面长时间白屏。
React为了解决这个问题选择另一种策略-时间切片,也就是先计算一部分更新, 然后让渡给渲染进程渲染,然后再进行下一步更新。以此往复。这样我从使用者的角度,就不会出现长时间白屏了。

fiber:
为了支持这种切片,我们需要把更新化成一个个单元,然后我们也必须有回复上一次计算进度的能力
所以react设计一种数据结构-fiber
每一个组件会被转化为一个fiber结构的对象,组成一个个单元。Fiber让我们有了回复上次中断的计算进度的能力

避免父组件数据更改导致子组件更新
父组件更改导致子组件更新的最大的问题
PureComponent(类组件)和React. m emo(函数组件)
React.Memo:
javascript
import Son from './Son.js';
import React from 'react';
let MemoSon =React.memo(Son);
javascript
<MemoSon></MemoSon>
避免state同样的值产生更新
避免state修改为同样的值,而产生无意义更新(PureComponent,函数组件本身就会判断)
props
如果组件使用了PureComponent或者React.Memo,虽然已经做到了如果父组件传的props没改变,就不会更新,但是我们特别注意父组件传入的方法,对象,数组这样的引用类型
- 用useCallback包裹传递给子组件的方法
- 非state对象,数组数据,要用useMemo包裹起来
React-router
三个版本
安装所需router库

使用步骤
- 通过BroserRouter或者HashRouter包裹要使用路由的根组件
- 使用Routes组件,定义路由显示区域
- 使用Route组件,定义具体路由规则
- 使用NavLink或者Link组件,定义调整链接

提供的一些其他重要组件
- Navigate-路由重定向
- Outlet,嵌套路由的子路由显示处
感受react的全局插件使用方式
React中没有vue那样的vue.use方法,react中使用一个插件,库,都是引入一个组件,然后把要使用该插件的部分包起来
动态路由和嵌套路由
嵌套路由
javascript
<Route path="/page2" element={<Page2 />}>
<Route path="son1"element={<Page2Son1 />}></Route>
<Route path="son2"element={<Page2Son2 />}></Route>
</Route>
Page2组件中使用Outlet显示:
javascript
import {Outlet} from "react-router-dom"
javascript
<Outlet></Outlet>
动态路由
javascript
<Route path="/page3/:id" Component={Page3}></Route>
获取路由参数

Params参数:
javascript
import {useParams}from "react-router-dom"
function Page3(){
let params =useParams();
console.log(params);
return <div>this is page3</div>
}
export default Page3
Query参数
javascript
import usesearchParams }from "react-router-dom"
function Page4(){
let[searchParams,setsearchParams]=useSearchParams();
console.log(searchParams.get("a"));
return <div>this is page4
<button onclick={()=>{
setSearchParams({
a:888,
b:666
}}>改变searchParams</button>
</div>
export default Page4
Location参数
javascript
import {useLocation }from "react-router-dom"
function Page1(){
let location = useLocation();
console.log(location);
return <div>this is page1</div>
}
export default Page1
js控制跳转地址
- V6-useNavigate创建跳转方法,然后跳转
- V5-this.props.history.push()
控制权限
控制某些页面不登录就不能访问
- 通过Navigate控制
- 直接不生成地址
异步路由
React做异步路由,要配合react本身的一个方法-Lazy和一个组件Suspense
javascript
import {Lazy,Suspense} from "react"
let LazyPage=Lazy(()=>{return import("/Page")})
Routes里只能放Route,Suspense需要包在Routes外
fallback中为过渡加载效果
javascript
<Suspense fallback={<h2>加载中</h2>}>
<Routes>
<Route
path="/Page"
Component={LazyPage}>
</Route>
</Routes>
</Suspense>
React状态管理
- React,没有专门的状态管理库,都是通用的js状态管理库,所以我们首先创建一个全局的数据储存和管理工具
- 通过其他工具,数据的修改能触发react页面得更新
React状态管理相关库

redux创建仓库

1、安装Redux及配套库
javascript
npm install redux react-redux
2、创建并导出store
store/index.js文件:

javascript
//引入 legacy_createStore 并修改别名
//(createStore已改名为legacy_createStore,现在的名字太长所以将以前的名字作为别名使用)
import { legacy_createStore as createStore } from "redux";
// 定义一个reducer函数, reducer函数是纯函数,不能修改state,只能返回新的state
// reducer函数返回的是修改后的state ,最后一定要return state,并且展开解除引用(state是引用类型,创建新的对象,不然state不会更新)
// action是修改数据的动作 ,type是动作类型,payload是动作携带的数据 ,可以叫其他名字 ,也可以不携带数据 ,默认是undefined
function reducer(state = { mes: 'hello' }, action) {
// 具体修改数据的行为
switch (action.type) {
case 'change':
// payload,也可以叫其他名字
state.mes = action.payload
// 最后一定要return state,并且展开解除引用
return { ...state }
case 'reset':
state.mes = 'hello';
return { ...state }//
default:
return state;// 默认返回state
}
}
let store = createStore(reducer);// 创建store 对象
export default store; // 导出store对象
3、获取store
javascript
import store from './store'
console.log("store", store)
let state = store.getState()
console.log("state", state)

- getState() 获取store
- dispatch() 修改store
- subscribe() 监听store的修改
4、修改
javascript
store.dispatch({type:'change',payload:'beybey' })
//因为这是一个单独的js库,虽然已经被修改了,但是不会触发React更新。所以需要使用手动触发更新。
触发更新方法
- store.subscribe监听store的修改,当store发生修改时,会执行回调函数。
可以将root.render 放在回调函数中,这样就可以在store修改时,重新渲染组件。但性能消耗大,不推荐。
javascript
store.subscribe(()=>{
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
})
- 使用react-redux
(1)、通过Provider组件,把store关联到目标项目
javascript
import { Provider } from 'react-redux';
import store from './store'
javascript
<Provider store={store}>
<App />
</Provider>);

(2)、利用connect连接到某组件
(3)、编写一些映射关系
javascript
// connect(mapStateToProps,mapDispatchToProps)(组件)
// 第一个参数是state的映射,说明你要把哪些state映射到该组件的props里,
// 第二个参数是dispatch的映射,说明你要把哪些dispatch映射到该组件的props里 ,如果不填这个参数,dispatch会直接映射在props中,可以直接使用props.dispatch方法
// 第三个参数是组件本身 ,返回一个组件
// 暴露组件时的操作
let ReduxApp = connect((state) => {
return {
mes: state.mes
}
},(dispatch)=>{return {
changeMes(){
dispatch({
type:'change',
payload:'beybey'
})
}
}})(App)
export default ReduxApp;
然后就可以在props中使用store和dispatch中映射的数据和方法了,此时数据修改后,视图会自动更新
javascript
function App(props){
return(
<>
<div>store中的mes:{props.mes}</div>
<button
onClick={()=>{
props.changeMes()
}}>修改store</button>
</>
)
}
let ReduxApp = connect((state) => {
return {
mes: state.mes
}
})(App)
export default ReduxApp;
原理:store中的数据修改不会发生组件更新,但通过映射把store中的数据映射在props中,props中的数据发生变化,组件更新
Redux多模块
store中有多个模块,使用combineReducers将多模块连接
javascript
import {
legacy_createStore as createStore,
combineReducers
} from "redux";
function mesReducer(state={mes:'hello'},action){
...
}
function numReducer(state={num:0},action){
...
}
let reducer = combineReducers({mesReducer,numReducer}) //合并reducer函数
let store = createStore(reducer);// 创建store 对象
export default store; // 导出store对象
取值时需要先选择模块
javascript
let ReduxApp = connect((state) => {
return {
mes:state.mesReducer.mes,
num:state.numReducer.num
}
})(App)
export default ReduxApp;
Redux 进 化版 @reduxjs / toolkit
React没有专门的地方存store,Redux写起来太难受了,所以有了Redux进化版:
@reduxjs/toolkit
基于Redux,创建出的仓库依然的Redux仓库,只是使用起来很简单

javascript
npm install @reduxjs/toolkit
创建仓库:
javascript
// 导入 Redux Toolkit 的 createSlice 和 configureStore 函数
import { createSlice, configureStore } from "@reduxjs/toolkit";
// 创建一个名为 "mesSlice" 的 slice,用于管理消息状态
let mesSlice = createSlice({
name: "mesSlice", // slice 的名称
initialState: { // 初始状态
mes: "hello" // 默认消息为 "hello"
},
reducers: { // 定义 reducer 函数
changeMes(state, action) {
// 更新消息状态为 action.payload 的值
state.mes = action.payload
}
}
})
// 创建一个名为 "numSlice" 的 slice,用于管理数字状态
let numSlice = createSlice({
name: "numSlice", // slice 的名称
initialState: { // 初始状态
num: 0 // 默认数字为 0
},
reducers: { // 定义 reducer 函数
addNum(state, action) {
// 将数字状态加 1
state.num += 1;
}
}
})
// 配置并创建 Redux store
let store = configureStore({
reducer: { // 指定 store 的 reducer
mesReducer: mesSlice.reducer, // 绑定 mesSlice 的 reducer
numReducer: numSlice.reducer // 绑定 numSlice 的 reducer
}
})
// 导出创建的 store,以便在应用中使用
export default store;
暴露使用仓库的组件时需要使用切片名:

javascript
let ReduxApp = connect((state) => {
return {
mes: state.mesReducer.mes
}
},(dispatch)=>{return {
changeMes(){
dispatch({
type:'mesSlice/changeMes',
payload:'beybey'
})
}
}})(App)
export default ReduxApp;
使用时可以用hook的方式
javascript
//hook的方式只能用于toolkit,只能用于函数组件
import {useSelector,useDispatch }from "react-redux";
function App(){
// 取出state
let num =useSelector((state)=>{
return state.numReducer.num
})
let dispatch = useDispatch();
return <div>
<span>{num}</span>
// 修改数据
<button onclick={()=>{
dispatch({
type: "numSlice/addNum"
})
}}>增加</button>
</div>
}
export default App;
还可以直接引入方法
在仓库中暴露
使用的文件中引入:import { addNum } from "./store"
直接使用:dispatch(addNum())
javascript
//hook的方式只能用于toolkit,只能用于函数组件
import { useSelector,useDispatch }from "react-redux";
import { addNum } from "./store"
function App(){
// 取出state
let num =useSelector((state)=>{
return state.numReducer.num
})
let dispatch = useDispatch();
return <div>
<span>{num}</span>
// 修改数据
<button onclick={()=>{
dispatch(addNum())
}}>增加</button>
</div>
}
export default App;
异步问题:
不能直接在仓库里写异步方法,需要额外处理
javascript
// 1、引入creatAsyncThunk方法
import { createSlice, configureStore,creatAsyncThunk } from "@reduxjs/toolkit";
// 2、创建并暴露切片 将异步操作放入该切片中
第一个参数:名字,随便起
第二个参数:具体的异步操作
export let changeNumThunk = creatAsyncThunk('numSlice/changeMes',async (params)=>{
let res = await new Promise(()=>{
setTimeout(()=>{
resolve(999)
},1000)
})
return res;
})
// 3、添加切片
let numSlice = createSlice({
name: "numSlice",
initialState: {
num: 0
},
reducers: {
addNum(state, action) {
state.num += 1;
}
},
//在此添加切片,并添加状况(进行中、成功、错误)
extraReducers:(chunk)=>{
chunk
.addCase(changeNumThunk.pending,()=>{
// 进行中
console.log("panding")
})
.addCase(changeNumThunk.fulfilled,(state,action)=>{
// 成功
state.num=action.payload;
})
}
})
javascript
4、在需要用到的地方引入
import {changeNumThunk} from "./store"
5、使用
dispatch(changeNumThunk())
其他:
vue和react的更新
- Vue
vue在get和set里触发更新。
Vue在get部分有一个重要的操作-依赖收集。
这样我们在更改了数据后,只会更新用到了这个数据的地方。做到最小的更新范围
- React
React的更新是调用方法时触发的,并没有依赖收集的过程所以他会更新整个组件树。
也就是会把子组件一起更新,即使更新的数据和子组件没有任何关系
严格模式:
<StrictMode>
严格模式只在开发模式下生效,生产上线时会去除,作用简要概括有两方面的作用
1,检测一些危险的操作(比如使用已经废弃api和不推荐的api)
2,会把一些生命周期执行两次,来检测额外副作用(比如render)
可以包App,也可以包某个组件
