React入门和快速上手

函数组件和类组件的区别

1、函数组件没有生命周期

2、函数组件没有this

3、函数组件通过hook来完成各种操作

  1. 函数组件本身的函数体相当于render函数
  2. 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>
  );
}

插值

单括号{}

插值可以使用的位置:

  1. 标签内容
  2. 标签属性

条件渲染

可以直接在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、源码层面上尽量弥补这个问题

  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,也可以包某个组件

相关推荐
wuhen_n2 小时前
组件测试策略:测试 Props、事件和插槽
前端·javascript·vue.js
Jiude2 小时前
Skill + MCP + Linear 自动化工作流:让 AI 包揽变更日志工作
前端·架构·cursor
zhensherlock2 小时前
Protocol Launcher 系列:Pika 取色器的协议控制(上篇)
前端·javascript·macos·typescript·github·mac·view design
蚂蚁家的砖2 小时前
基于 Vue 3 + Cesium 的 DJI 无人机航线规划系统技术实践
前端·无人机
inksci2 小时前
推荐动态群聊二维码制作工具
前端·javascript·微信小程序
wuhen_n2 小时前
Vue3 单元测试实战:从组合式函数到组件
前端·javascript·vue.js
郑州光合科技余经理4 小时前
海外O2O系统源码剖析:多语言、多货币架构设计与二次开发实践
java·开发语言·前端·小程序·系统架构·uni-app·php
arvin_xiaoting9 小时前
OpenClaw学习总结_I_核心架构_8:SessionPruning详解
前端·chrome·学习·系统架构·ai agent·openclaw·sessionpruning
工程师老罗10 小时前
Image(图像)的用法
java·前端·javascript