react 学习笔记 李立超老师(学习中~) | JSX - React组件 - 钩子函数

文章目录

  • react学习笔记01
    • 入门
      • 概述
      • React 基础案例HelloWorld
        • 三个API介绍
      • JSX
        • JSX 解构数组
      • 创建react项目(手动)
      • 创建React项目(自动) | create-react-app
      • 事件处理
      • React中的CSS样式
        • 内联样式 | 内联样式中使用state (不建议使用)
        • 外部样式表 | CSS Module
    • React组件
      • 函数式组件和类组件
        • 生成一组标签/组件
      • props 父组件给子组件传属性/方法
        • 给组件设置className样式不生效
      • state 维护组件的响应式状态
        • useState(stateInitValue)
      • Ref 获取DOM对象
      • 非受控组件与受控组件
        • 数据的双向绑定
        • 子组件给父组件传值 = props传递函数 + 子组件调用函数
        • vue中v-if与v-show的React写法
        • Portal 将元素渲染到指定位置
      • Fragment 组件
      • Context 祖先组件向子孙组件传值
      • Effect 副作用
        • setState()在函数组件中的执行流程
        • React.StrictMode
        • useEffect()
        • Effect的清理函数
      • Reducer 整合器 钩子函数
      • React.Memo 缓存组件

react学习笔记01

学习视频 react18 李立超

学习中get到的新用法

  1. Date类的toLocalString方法,可以更为灵活的处理Date类。

  2. 标签属性中闭包的使用
    举例:仅在删除状态时使用id,不需要单独传递id属性。

    const logItemDate = logsData.map(item=> <LogItem  onDelLog ={()=> delLog(item.id)}>)
    
  3. 移动端适配 rem + vw
    可以使用vw获取视口宽度,将font-size设置单位为vw,然后结合rem做适配。
    1vw = 视口宽度的1% -> 100vw = 视口的宽度

    一般设置html的font-size值 = 屏幕宽度/设计稿宽度,但移动端比如375px计算出的font-size值小于12px会造成一些错误和奇怪的问题,因此把比例扩大100倍

    为了使比例不变,相应的设计图元素使用时设计图元素大小/100 rem

    根html的font-size值 = 屏幕宽度/设计稿宽度*100 
    font-size = 100vw/设计稿宽度*100
    

入门

概述

AJAX+DOM可以实现网页的局部刷新,但是新数据不能直接在网页中显示,需要通过DOM将数据转换为网页中的节点。

react帮助我们根据不同的数据来快速构建用户项目,同时在构建过程中确保其流畅度。

react特点

1.使用虚拟DOM而不是真正的DOM

2.声明式编码(声明式:结果为导向,不关心结果 命令式:一行代码一个命令)

3.支持服务器端渲染

React 基础案例HelloWorld

入门案例采用外部引入脚本使用(正常开发使用包管理器)

  • react.development.js reactreact核心库,只要使用react就必须要引入。下载地址
  • react-dom.development.js react-domreactdom包,使用react开发web应用时必须引入。下载地址
  • babel.min.js 浏览器不能识别JSX,利用该babelJSX转换为JS代码。下载地址

1.引入脚本

<script src="../script/react.development.js"></script>
<script src="../script/react-dom.development.js"></script>

2.创建一个React元素

React.createElement(组件名/元素名,元素中的属性,元素的子元素/内容)

const reactDiv = React.createElement('div',{},'我是react创建的div'); 

3.获取根元素对应的React元素

ReactDOM.createRoot(Dom元素);

// html
<div id="root"></div>
// js
const root = ReactDOM.createRoot(document.getElementById('root'));

4.将reactDiv渲染到React根元素中

root.render(reactDiv)
三个API介绍
  • React.createElement(type,[props],[...children]) 用来创建React元素(并不是ReactDom,所以这里使用React调用)

    • class属性需要使用className属性代替。

    • type如果是标签名(元素)需要全小写,首写母大写会被认为是组件

    • 在设置属性时,事件名应遵守驼峰命名法,事件值需要是一个函数,不能是console.log(xx)这种表达式。如果直接写一个函数调用语句,则在绑定事件时就会被调用(之后事件不会被触发)

    • React元素是一次性的,一旦创建就无法修改,只能使用新创建的元素进行替代

  • ReactDOM.createRoot(container[,options]);用来创建React的根容器,根容器用来放置React元素

    • 将参数的DOM元素转换为React根元素
  • ReactDOM实例.render(ReactElement)React元素渲染到根元素中

    • DOM根元素中所有的内容都会被删除(不会修改DOM根元素本身),被React元素转换而成的DOM元素替换
    • 重复调用render(),React会将两次虚拟DOM进行对比,确保只修改发生变化的元素,对DOM做最少修改。首次调用时,容器节点里的所有DOM都会被替换,后续的调用则会使用ReactDOM差分算法(diff)进行更新

JSX

上述方法中React.createElement('button', {}, '我是按钮')还是命令式编码方法,告诉reactcreateElement去创建一个button按钮,该按钮没有属性,内容为我是按钮。

声明式编程结果导向,告诉结果,不关系过程怎么样。

const button = <button>我是按钮</button>; // 告诉react我需要一个button按钮元素,不关心react如何创建

React中可以通过JSXJavaScript Syntax Extension)来创建React元素,JSX 让我们以类似于HTML 的形式去使用 JSJSXReact中声明式编程的体现方式。

JSX需要被翻译为JS代码,才能被React执行。 要在React中使用JSX,必须引入babel来完成“翻译”工作。

  • JSX就是React.createElement()的语法糖,最终都会转换为以调用React.createElement()创建元素的代码。

  • JSX在执行之前都会被babel转换为JS代码

    <!-- 引入babel -->
    <script src="script/babel.min.js"></script>
    <!--设置js代码被babel处理-->
    <script type="text/babel">
        const div = <div>
            我是一个div
            <button>我是按钮</button>
        </div>;
     	const root = ReactDOM.createRoot(document.getElementById('root'));
        root.render(div);    
    </script>
    
  • JSX不是字符串,不需要加引号

const div =  <div>我是一个div</div>  // 正确写法   
  • JSXhtml标签应该小写开头,React组件应该大写开头
<div> // 小写html标签
<Div> // 大写组件
  • JSX有且只有一个根标签

  • JSX的标签必须正常结束(自结束标签必须写/)

const input = <input type="text" / >
  • JSX中使用{}嵌入表达式(有值的语句就是表达式)
const name = "ranran"
const div = <div>{name}</div> // 才会显示ranran,没有括号会把name识别为字符串
  • 如果表达式值为空值、布尔值、undefined,将不会显示

  • JSX属性可以直接在标签中设置

    • 事件绑定需要是一个函数,而不能直接是函数调用(绑定时就会被触发,不会延迟触发)
    • className代替class
    • style必须使用对象设置,属性名必须用驼峰命名法
const div = <div onClick="()=>{console.log('ranran')}" 
 style={{backgroundColor: "yellowgreen", border: '10px red solid'}}
></div> // 外面的大括号表示style必须使用对象设置,里面的对象表示给他设置的值是一个对象(有多个样式)
  • 在语句中可以操作JSX

    const name = 'ranran';
    const lang = 'cn';
    
    let div;
    if(lang === 'en'){
        div = <div>hello {name}</div>;
    }else if(lang === 'cn'){
        div = <div>你好 {name}</div>;
    }
    const root = ReactDOM.createRoot(document.getElementById('root'))
    root.render(div)
    
JSX 解构数组

JSX在解构{}的内容时,如果内容是数组则会自动将其展开。

//页面:孙悟空猪八戒沙和尚
const data = ['孙悟空', '猪八戒', '沙和尚'];
const div = <div>{data}</div> 
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(div)

/*
· 孙悟空
· 猪八戒
· 沙和尚
*/
const data = ['孙悟空', '猪八戒', '沙和尚'];
const list = <ul>{data.map(item => <li>{item}</li>)}</ul>;
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(list)//页面:孙悟空猪八戒沙和尚
const data = ['孙悟空', '猪八戒', '沙和尚'];
const div = <div>{data}</div> 
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(div)

/*
· 孙悟空
· 猪八戒
· 沙和尚
*/
const data = ['孙悟空', '猪八戒', '沙和尚'];
const list = <ul>{data.map(item => <li>{item}</li>)}</ul>;
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(list)

React通过虚拟DOMReact元素和原生DOM元素进行映射

当我们调用root.render时。页面就会发生重新渲染
React通过diff算法将新的虚拟DOM和旧的比较,找到发生变化的元素,并且只对变化的元素进行修改。

数组中(当前数组)每一个元素都需要设置一个唯一key
重新渲染页面时,Reactkey值会比较key值相同的元素,没key值会按照顺序进行比较。

  1. 开发中一般会采用数据的 id 作为 key
  2. 尽量不使用元素的 index 作为 key 索引会跟着元素顺序的改变而改变,所以使用索引做 key 跟没有 key 是一样的。 唯一的不同就是,控制台的警告没了。 当元素的顺序不会发生变化时,用索引做 key 也没有什么问题。
const data = ['孙悟空', '猪八戒', '沙和尚'];
const list = <ul>{data.map(item => <li key={ item }>{ item }</li>)}</ul>;
// const list = <ul>{data.map((item,index) => <li key={ index }>{ item }</li>)}</ul>;
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(list)

创建react项目(手动)

React官方为了方便开发,提供react-scripts包(①打包②测试服务器-根据代码变化自动刷新避免改一点就重新打包),包中提供了项目开发中的大部分依赖。
由于提供了配置好的工具,我们一些操作就要符合约定。

使用包管理器管理项目,没有办法直接放在网页中运行。需要经过webpack打包,才能在浏览器中正常执行。

  1. 创建React
根目录
    - public(可以web直接访问的文件,不用打包就可以浏览器访问的静态资源)
        - index.html (入口文件,必须有,首页模板打包时以此为模板生成最终的index/html | 添加标签 <div id="root"></div>- src(源码,JS源代码)
        - index.js(必须,webpack打包文件的入口,该文件会被自动引入public/index.html中)
  1. pnpm init 初始化项目,生成package.json文件(大部分时候这一步可以省略)
  2. pnpm install react react-dom react-scripts 安装项目依赖
  3. 编写代码src/index.js
// 引入ReactDOM
import ReactDOM from 'react-dom/client';

// 创建一个JSX
const APP = <div><h1>这是一个react项目</h1></div>

// 获取一个根元素
const root = ReactDOM.createRoot(document.getElementById('root'));
// 将APP渲染进根容器
root.render(APP);
  1. 运行项目
  • pnpm react-scripts build 打包项目,一般开发完成之后需要上线时使用该命令进行打包。
    初次需要输入y确认。打包时需要默认配置,会询问是否添加默认配置。

在这里插入图片描述

正常情况,右键打开会报错。因为打包好的文件需要部署在服务器上运行,而不是直接使用浏览器打开。每次打包后路径都是这样需要手动修改。

在这里插入图片描述

  • pnpm react-scripts start 开发中使用的命令
    通过webpack启动内部的测试服务器,可以实时对更新代码进行编译。这个命令太长,可以在package.jsonscripts 选项中配置命令,下次可以使用命令pnpm start

    "scripts": {
      	"start": "react-scripts start"
    }
    

react 一定需要两个文件

  • public/index.html:入口文件,首页模板打包时以此为模板生成最终的index/html - 提供dom root根节点
  • src/index.jswebpack打包文件的入口,该文件会被自动引入public/index.html中 - 将root转化为react根节点元素后,将react元素挂载到react根节点中

创建React项目(自动) | create-react-app

命令:npx create-react-app 项目名

除了public/index.htmlsrc/index.js必须保留外,其他的东西都是可以删除的。

/*
  reate-react-app 创建index.js
  其中<React.StrictMode>使用严格模式渲染React元素 - 可以不使用
*/
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    aaa
  </React.StrictMode>
);


事件处理

react 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:

  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写。

  • 使用 JSX 语法时需要传入一个函数作为事件处理函数。事件绑定需要是一个函数,而不能直接是函数调用(绑定时就会被触发,不会延迟触发,等于将函数的返回值给了该事件)

    // 传统 HTML
    <button onclick="activateLasers()">
      Activate Lasers
    </button>
    // React
    <button onClick={activateLasers}>  
       Activate Lasers
    </button>
    
  • React事件通过会传递事件对象event,但其不同于原生的事件对象,是React包装后的事件对象,该对象已经处理了跨浏览器的兼容性问题。

    React中事件回调函数不能通过返回false阻止默认行为,必须显式地使用event事件对象的preventDefault方法

    // 传统 HTML
    
    <form οnsubmit="console.log('You clicked submit.'); return false">
      <button type="submit">Submit</button>
    </form>
    
    // React
    function Form() {
      function handleSubmit(e) {
        e.preventDefault();    
        console.log('You clicked submit.');
      }
    
      return (
        <form onSubmit={handleSubmit}>
          <button type="submit">Submit</button>
        </form>
      );
    }
    

React中的CSS样式

内联样式 | 内联样式中使用state (不建议使用)

style必须使用对象设置,属性名必须用驼峰命名法

const StyleDemo = () => {
    return (
        <div style={{color:'red', backgroundColor:'#bfa', fontSize:20, borderRadius:12}}>
            我是Div
        </div>
    );
};

export default StyleDemo;

当样式过多,JSX会比较混乱,可以使用变量去保存对象

import React from 'react';

const StyleDemo = () => {
    const divStyle = {color: 'red', backgroundColor: '#bfa', fontSize: 20, borderRadius: 12}

    return (
        <div style={divStyle}>
            我是Div
        </div>
    );
};

export default StyleDemo;

内联样式中使用state
当样式是动态时,可以在样式中使用state变量。

import React, {useState} from 'react';

const StyleDemo = () => {

    const [showBorder, setShowBorder] = useState(false);

    const divStyle = {
        color: 'red',
        backgroundColor: '#bfa',
        fontSize: 20,
        borderRadius: 12,
        border: showBorder?'2px red solid':'none'
    };

    const toggleBorderHandler = ()=> {
      setShowBorder(prevState => !prevState);
    };

    return (
        <div style={divStyle}>
            我是Div
            <button onClick={toggleBorderHandler}>切换边框</button>
        </div>
    );
};

export default StyleDemo;
外部样式表 | CSS Module

外部样式是指将样式编写到外部的css文件中,直接通过import引用。

直接import引入的样式都是全局样式,其他组件也看得见这个样式。如果不同的样式表中出现了相同的类名,会出现相互覆盖情况。

import './index.css'

CSS Module
使用CSS Module后,网页中元素的类名会自动计算生成并确保唯一

如果引用同一个模块,计算出来的类名是相同的。

CSS ModuleReact中已经默认支持(前提是使用了react-script)

  1. 文件样式的文件名为xxx.module.css
  2. 在组件中引入样式的格式为import xxx from './xxx.module.css'
  3. 设置类名时通过xxx.yyy的形式来设置
/*
StyleDemo.module.css
*/
.myDiv{
    color: red;
    background-color: #bfa;
    font-size: 20px;
    border-radius: 12px;
}

/*
StyleDemo.js
*/
import Styles from './StyleDemo.module.css';

const StyleDemo = () => {
    return (
        <div className={Styles.myDiv}>
            我是Div
        </div>
    );
};

export default StyleDemo;

React组件

组件需要遵守的规则

  • 组件名首字母必须大小(小写字母开头的组件会被视为原生DOM标签)
  • 组件中只能有一个根元素

函数式组件和类组件

React中定义组件有两种方式

  • 基于函数的组件 - 函数式组件(推荐) :函数组件是返回JSX普通函数
  • 基于类的组件 - 类组件

函数式组件

函数组件是返回JSX普通函数

//1.创建函数式组件 App.js
const App = () => {
  return <div>我是App组件!</div>
};
// 2.导出App
export default App;


// index.js
// 3.引入App
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById('root'));
// 4.React组件可以直接通过JSX渲染
root.render(<App/>);	//root.render(App()); 也可以,只是<App/>内部做了更多的事情。

类组件

1.创建一个ES6 class,并继承于React.Component

2.添加一个render方法,方法的返回值为JSX

import React from "react"
//1.创建类组件  必须要继承React.Component
class App extends React.Component{
    constructor(props){ // 参数props接受父组件的传值
        this.state = 'xxx' //state的使用
    }
    // 2.添加render方法
    render(){
        return <div>我是一个类组件{this.props}</div>
    }
}

// index.js
// 3.引入App
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById('root'));
// 4.React组件可以直接通过JSX渲染
root.render(<App/>);	//root.render(App()); 也可以,只是<App/>内部做了更多的事情。

props、state、ref

  • 类组件的props存储在类的实例对象中,可以通过this.props访问。

  • 类组件中state统一存储到了实例对象的state属性中,可以通过this.state来访问,通过this.setState()修改。

    • 通过this.setState修改state,只修改设置了state的属性,并不会修改没设置的第一层属性。
  • 通过React.createRef()(函数式为useRef)创建属性存储DOM对象,同样通过对象.current获取

  • 事件回调函数需要定义为类的方法,建议使用箭头函数,这样this指向的是react实例。否则函数里的this会执行设置事件的dom元素

import React,{ Component } from 'react'

class App extends Component{
 	state = {
        count:0,
        age:{} 
    }  
    divRef = React.createRef();
    // 事件回调函数需要定义为类的方法,建议使用箭头函数,这样this指向的是react实例。否则函数里的this会执行设置事件的dom元素
    clickHandler = ()=>{
        // 写法1:this.setState({count:this.state.count+1})
        // 写法2
        this.setState(prevCount => {
            return {
                count:prevCount+1;
            }
        })
    }
    retnder(){
        return <div>
            <h1 ref={ divRef }>this.props<h1>
            <h1 @onClick={this.clickHandler}>this.state.count<h1>   
        </div>
    }
}
生成一组标签/组件

react中对于根据数组数据产生一组标签或者一组组件,没有类似vuev-for指令,一般使用{ data.map(JSX) }的语法进行生成。

const App= () => {
	const data = [{title:"1",id:"0"},{title:"2",id:"1"},{title:"3",id:"2"}];
    return <div>
    { data.map(item => <Button key={item.id} titile={item.title}></Button >) }
    </div> 
    
    /*
     写法1 
     return <div> { data.map(item => <Button key={item.id} titile={item.title} />) }  </div> 
    写法2:将对象的每个属性都传递
    return <div> { data.map(item => <Button {...item}>) </div>
	}
    */
};

export default App;

props 父组件给子组件传属性/方法

父组件通过使用子组件时,定义自定义参数传递属性/方法。子组件通过参数props接收(函数式组件的第一个参数)。
react中的props类似vue中的props是只读属性是无法修改的

props.children中可以获取到父组件中,子组件标签体内部的值。

// 父组件
<Button bgColor='red' color={ color }>我是一个按钮</Button>

//子组件
const Button = (props) => {
    return <button style={{backgroundColor:props.bgColor, color:props.color}}>{props.children}</button>;
};

export default Button;
给组件设置className样式不生效

原因

className会被认为是一个属性传递给子组件,需要在子组件的根元素使用className={props.className}接收。

state 维护组件的响应式状态

React中,当组件渲染完毕后,再修改组件中的变量,不会使组件重新渲染。state相当于一个变量,只不过在React中进行了注册。React会监控整个变量的变化,当state发生变化时,会自动触发组件的重新渲染。

页面的渲染靠的是render函数

state概述

stateprops类似,都是一种存储属性的方式。

  • state只属于当前组件(组件的私有属性),其他组件无法使用。
  • state的值是对象,当其内容发生变化相关组件会一起刷新
useState(stateInitValue)

通过钩子函数useState(stateInitValue)创建stateReact中钩子函数只能用于函数组件或自定义钩子。

  • 参数是整个state变量的初始值

  • 函数返回一个数组[stateVariable,setStateFunction],第一个元素是state变量的初始值(只用于显示),第二个元素是修改该变量的函数(函数的参数为新值)。调用修改函数修改state变量的值(state值发生变化)会触发组件的重新渲染,直接修改state变量不会触发组件的重新渲染。

    import { useState } from 'React'
    const [stateVariable,setStateFunction] = useState(1);
    

组件重新渲染等同于函数重新执行,函数体的代码会再次调用(赋值语句等会重新赋值)。setState()是由钩子函数useState()生成的,useState()会保证数组的每次渲染都会获取到相同的setState()

注意点

  1. state值是一个对象时,setState()修改时,使用新的对象去替换已有对象。
const [user, setUser] = useState({name:"ranran",age:18})
user.name = "xxx"; 
serUser(user); // user是对象,对象的地址没有发生变化,所以不会引起组件重新渲染

/* 
解决方案:将其拷贝给另一个新对象,修改新对象的属性
*/
setUser({...user,name:"xxx"}) // 后面的name会覆盖前面的name
  1. 通过setState()去修改一个state时,并不表示修改当前的state,修改的是组件下一次渲染的state

  2. setState()会触发组件的异步渲染(并不是马上调用就渲染,放入事件循环队列中等待执行),所以当调用setState()需要使用state值时,可能出现计算错误。

    因为setState()修改的是下一次渲染的state,如果下一次渲染还没进行前又调用了setState(),此时state还是旧值,所以就会出现计算错误。

    解决办法 : 通过传递回调函数的形式修改state

    回调函数的返回值会成为新的state值,回调函数执行时React会将最新的state值作为参数传递。
    总结:如果setState()中需要用到旧值,参数都采用函数的形式。

    setCount(state => state+1); // 传递参数,React会保证参数的state是最新值
    

Ref 获取DOM对象

Refreference的简写,用来获取真实DOM的引用。

  • 使用useRef()钩子函数获取DOM对象
    • 1.通过useRef()钩子函数返回一个普通JS对象,React会自动将DOM对象传递到该对象的current属性中。
    • 2.被引用的DOM元素上添加ref属性,值为上述的对象。
      根据描述,直接创建一个有current属性的普通JS对象可以实现相同的效果。

两种方法的不同点

  • 自定义对象方法,组件每次重新渲染,都会创建一个新对象
  • 使用useRef()函数返回的对象的声明周期和组件的声明周期一致,所以每次重新渲染,该ref对象都是原来的。
import {useRef} from 'react';

const MyComponent = () => {

    const divRef = useRef();
	/*
	const divRef = {current:null}
    */
    const clickHandler = () => {
        console.log(divRef);
    };

    return (
            <div ref={divRef} onClick={clickHandler}>一个div</div>      
    );
};

export default MyComponent;

非受控组件与受控组件

非受控组件:表单中的数据来源于用户填写的组件,表单元素的值不会更新state,输入数据都是现用现取的。

受控组件:使 Reactstate 成为唯一数据源,由state控制表单。

数据的双向绑定

将表单的value绑定为state数据,表单的onChange事件触发时,通过事件对象event获取到新值,然后使用setState修改state的值为新值。

import { useState } from 'react';
import './index.css';

const Demo = () => {
    // 如果有多个表单,可以将表单数据设置为一个对象
    const [inputValue, setInputValue] = useState('');
    return (
        <>
            <input
                type="text"
                className="inputDemo"
                value={inputValue}
                onChange={e => {
                    setInputValue(e.target.value);
                }}
            />
        </>
    );
};

export default Demo;
子组件给父组件传值 = props传递函数 + 子组件调用函数
  1. 在父组件中,使用props给子组件传递一个自定义事件
  2. 在子组件中将需要传递的数据作为函数参数,调用函数
// 父组件
<LogsItem onSavaLog={ savaLogHandler }>

// 子组件
const LogsItem = (props) => {
	props.savaLogHandler("需要传递的数据");
}

关于传递setState函数给子组件的一些说法:尽量不要这样做,state在哪里,setState尽量就在哪里。

vue中v-if与v-show的React写法
  • v-if-v-else配对出现 可以使用条件判断
  • v-show/仅有v-if 可以使用&&
// v-if/v-else 可以使用条件判断
控制变量 ? v-if显示的 : v-else显示的

// v-show/仅有v-if 可以使用&& 
控制变量 && v-show显示的

如果显示出来的组件内部需要修改外部的控制变量,react中一般的做法时将函数作为参数传递。因为控制变量在外部,内部只需要调用该函数,外部修改控制变量的值。

Portal 将元素渲染到指定位置

React中,父组件引入子组件后,子组件会直接在父组件内部渲染。换句话说,React元素中的子组件,在DOM中,也会是其父组件对应DOM的后代元素。

问题描述
每个组件都是相同的构成(想象成一个列表),组件内部包含一个子组件,该子组件的作用是生成一个遮罩覆盖全局。
组件1开启相对定位,遮罩开启固定定位(不一定是和这个例子相同的定位方式,这里举例)
由于组件1组件2组件3的 z-index:1,后面的组件会覆盖前面的。所以组件1中的遮罩出现时,覆盖不了组件2组件3,即使遮罩的z-index:999(理解为在组件1内部元素的层级中占比很高,但不影响组件1的层级),但组件1和其他兄弟组件层级相同(父元素组件1都被覆盖了子元素肯定被一起覆盖)。


结构问题:遮罩需要遮住视图不应该作为组件123的子组件,如果必须这样写,解决办法是使用Portal 将组件渲染到指定位置

ReactDOM.createPortal(需要渲染的元素,传送到的指定位置):渲染元素时将元素渲染到网页中的指定位置
1.在index.html中添加一个新的元素

<div id="root"></div>
<!--这个容器用来专门渲染遮罩层-->
<div id="backdrop"></div>

2.在组件中通过ReactDOM.createPortal()将元素渲染到新建的元素中

const backdropDOM = document.getElementById('backdrop');

// 在其他组件内部正常使用Backdrop组件,但是该组件渲染时会被传送到专门渲染遮罩层的容器中渲染,会脱离原来的结构
const Backdrop = () => {
  return ReactDOM.createPortal(
  <div>
  	{props.children}
  </div>,
      backdropDOM
  );
};

Fragment 组件

React中,JSX必须有且只有一个根元素,这导致在某些情况需要添加一个额外的父元素(并没有实际意义)

  • React提供了Fragment组件,Fragment可以让你聚合一个子元素列表,并且不在DOM中增加额外节点
  • <></>Fragment的语法糖,<></> 语法不能接受键值或属性,但Fragment可以传递 key 属性
import React from 'react';

const MyComponent = () => {
    return (
        <React.Fragment>
            <div>我是组件1</div>
            <div>我是组件2</div>
            <div>我是组件3</div>
        </React.Fragment>
        /*
        <>
            <div>我是组件1</div>
            <div>我是组件2</div>
            <div>我是组件3</div>
        </>
        */
    );
};

export default MyComponent;

Context 祖先组件向子孙组件传值

Context相当于一个公共的存储空间

创建content

// defaultValue存储的值
export const MyContext = React.createContext({
	name:xxx,
    age:xxx,
});

访问到Context中的数据

  • 方式1:通过Consumer标签来访问到Context中的数据(不常用)

    该组件内部必须使用函数,解析时会调用该函数,将创建的defaultValue作为该函数的参数传递。

    import React from 'react';
    import { MyContext } from '../store/test-context';
    
    const MyContext = () => {
    
        return (
            <MyContext.Consumer>
                {(ctx)=>{ // 上述案例中的defaultValue
                    return (
                        <ul>
                            <li>{ctx.name}</li>
                            <li>{ctx.age}</li>
                        </ul>
                    );
                }}
            </MyContext.Consumer>
    
        );
    };
    export default MyComponent;
    
  • 方式2:使用钩子函数useContext(context参数)获取到context,该钩子函数会返回Context中的数据

    import React, {useContext} from 'react';
    import { MyContext } from '../store/test-context';
    
    const MyComponent = () => {
        const ctx = useContext(MyContext);
        return (
            <ul>
                <li>{ctx.name}</li>
                <li>{ctx.age}</li>
            </ul>
        );
    };
    
    export default MyComponent;
    

​ 一般不会将数据直接放在Context,因为这样写是死数据并且与state响应式数据没什么关系,不会触发组件的重新渲染。所以React还提供了Provider组件,用于在数据所在的组件中指定Context值。

import React from "react";
import MyComponent from "./component/MyComponent";
import { MyContext } from "./store/test-context";

// 数据所在的组件
const App = () => {
	// 指定context的值
    return <MyContext.Provider value={{name:'猪八戒', age:28}}>
        /* Provider的子组件 */
        <MyComponent/>   
    </MyComponent.Provider>;
};

export default App;

Provider设置在外层组件中,通过value属性来指定Context的值。这个Context值在所有的Provider子组件中都可以访问。Context的搜索流程类似vueprovide inject

Effect 副作用

组件每次重新渲染,组件的函数体就会执行。

有一部分逻辑如果直接写在函数体中,会影响到组件的渲染,这部分会产生“副作用”的代码,是不能直接写在函数体中。

例如,如果直接将修改state的逻辑编写到了组件之中,每次函数体执行设置基础值,state变量又引起组件的更新,就会导致组件不断的循环渲染,直至调用次数过多内存溢出。

setState()在函数组件中的执行流程

setState()会调用dispatchSetDate()方法,dispatchSetDate()方法的主要逻辑

  • 判断组件当前处于什么阶段(渲染阶段 |非渲染阶段 )

    • 处于渲染阶段:不会检查state值是否相同,在此时直接将setState设置的值放入渲染队列等待渲染

    • 处于非渲染阶段:检查setState设置的值与之前的值是否相同。如果值不同,对组件进行重新渲染;如果值相同,则不对组件进行重新渲染。

处于渲染阶段案例

const App = () => {
	const [count,setCount] = uesState(0);
    // 会触发Too many re-renders报错 
    // 调用的时候处于渲染阶段,因为div没有渲染到页面上,所以会引发重新渲染,再次调用组件函数。也就是说无限循环,不会退出渲染阶段。
	setCount(0); 
    return (
    	<div>
        	{count}
        </div>
    )
}

待解决问题:之前学习说setState()会触发组件的异步渲染,但setCount的时候组件还处于渲染阶段,所以不会等待本次渲染完成(或者说不会渲染本次),直接开始重渲染?
=> 或者说之前学习说的太笼统了,现在对组件的状态进行了区分,明确了setState的执行流程?

处于非渲染阶段案例

第一次点击按钮count = 0 -> 1,组件重新渲染。

第二次点击按钮count = 1 -> 1,组件重新渲染。

第三次点击按钮count = 1 -> 1,组件没有重新渲染。

这是因为当值相同时,React在某些情况下(通常发生在值第一次相同时)会继续执行当前组件的渲染(这里指的时组件函数执行并更新页面),这次渲染不会产生实际效果(这里应该仅重新执行组件函数并不更新页面,不触发刷新没有什么用??)并且不会触发子组件的渲染。

const App = () => {
	const [count,setCount] = uesState(0);

	const clickHandler = ()=>{
        setCount(1); 
    }
    return (
    	<div onClick={ clickHandler }>
        	{count}
        </div>
    )
}
React.StrictMode

脚手架自动生成的index.jx中使用了该组件,该组件表示react自身开启严格模式,开启后react会自动去检查组件中是否有副作用的代码(并不是很智能)。

root.render(
	<React.StrictMode>
    	<App/>
    </React.StrictMode>
)

React的严格模式,在开发模式下,会主动重复调用一些函数,以使副作用出现。这些函数会被调用两次,如果安装了React Developer Tool,调试作用的第二次调用会显示为黑色。

  • 类组件的 constructor, render, 和 shouldComponentUpdate 方法
  • 类组件的静态方法 getDerivedStateFromProps
  • 函数组件的函数体
  • 参数为函数的setState
  • 参数为函数的useState, useMemo, useReducer
useEffect()

useEffect(()=>{},[])是一个钩子函数,可以将会产生副作用的代码编写到useEffect函数的回调中

  • 参数函数会在组件每次渲染完毕(dom渲染完毕)后执行。
  • 在数组中可以指定Effect的依赖项,指定后,只有当依赖发生变化时,Effect才会被触发。通常会将Effect中使用的所有变量都设置为依赖项。
    • 如果依赖项设置为空数组,那么Effect只会在组件初始化时触发一次。
Effect的清理函数

useEffect的回调函数中,可以返回一个函数,该函数被称为清理函数,该函数会在下次Effect执行前调用。

可以在清理函数中,清除上一次Effect执行所带来的影响。

const Filter(){
    // 初始化 先Effect回调再清理函数
    // 其他情况,先清理函数再Effect
    const [keyword,setKeyword] = useState();
    useEffect(()=>{ 
        const timer = setTimeout();//初始化时,先设置一个定时器A
    	// 清理函数
        return ()=>{
            /*
            这里形成了一个闭包,timer是定时器A的值。
            下一次Effect执行前,先清理定时器A再生成新的定时器
            */
            clearTimeout(timer);     
        }
	},[keyword])
}

Reducer 整合器 钩子函数

Reducer提供新使用State的方式 ,适用于复杂的State

语法: const [state, dispatch] = useReducer(reducer, initialArg, init);

  • 参数

    • reducer整个函数,对State的所有操作都应该定义在该函数中,该函数的返回值设置为state的新值。
      • reducer函数执行时会受到两个参数,第一个参数为最新的state,第二个参数action对象,该对象会存储dispatch发送的指令(也可以通过不同的key传参)。
    • initialArgstate的初始化值,作用和useState()中值一样
  • 返回值

    • state用于获取state的值
    • dispatch是修改state的派发器,通过派发器发送操作State的命令。

然后执行reducer函数。

// 整合器根据不同的指令执行不同的操作
const [count,countDispatch] = useReducer((state,action)=>{
    if(action.type === 'ADD'){
        return state+1;
    }else if(action.type === 'SUB'){
        return state-1;
    }
    return state; //防止state丢失
},1)
const addHandler = () => {
    countDispatch({type:'ADD'})
}
const subHandler = () => {
    countDispatch({type:'SUB'})
}

问题useReducer在组件中定义,所以每次渲染useReducer都会执行,reducer函数都会重新创建一次(即使之后会回收,但是会重复创建)。

解决办法:为了避免reducer函数的重创建,通常将reducer函数定义在组件外部

React.Memo 缓存组件

React组件会在两种情况下发生重新渲染,第二种情况并不总是需要的。

  1. 当组件自身的state发生变化

  2. 当组件的父组件重新渲染

React.memo()该方法是一个高阶函数,参数是一个组件A,返回包装过的新组件B。

包装过的新组件B具有缓存功能(类似vue中的keepAlive),只有组件A的props发生变化,才会触发组件重新渲染。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/241416.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

郑重声明 | 【机器学习之心】无小号,打者本人旗号干活的其他号,本人概不负责,可笑,未经过我同意就成你们的合作账号了?

打着本人旗号的号如下&#xff08;主要是天天Matlab团队&#xff09; 可笑&#xff0c;未经过我同意就成你们的合作账号了&#xff1f; 天天Matlab科研工作室 科研助手大师 机器学习之星主&#xff08;这个恶心&#xff0c;名字都仿我&#xff09; 海神之光 上述号没有程序售…

亚马逊鲲鹏系统:防关联技术守护您的账户安全

亚马逊买家账号注册是一项相当简便的操作&#xff0c;但当涉及到批量注册时&#xff0c;我们就需要更加注意防关联的问题。对于那些对此领域不够熟悉的朋友们&#xff0c;可以使用亚马逊鲲鹏系统&#xff0c;这款系统能够为我们提供一站式的解决方案。该系统不仅支持买家账号的…

Windows提权方法

简介 内网提权&#xff0c;本意为通过某些服务的漏洞&#xff0c;从而获取到该服务器的shell&#xff0c;进而内网渗透&#xff0c;最终从普通用户变成超级管理员的一个过程 以下是一些常见的内网提权原理和方法&#xff1a; 横向移动&#xff1a;攻击者通过在内网中的一台受感…

(五)STM32 NVIC 中断、优先级管理及 AFIO 时钟的开启

目录 1. 中断相关知识简介 1.1 什么是中断 1.2 什么是内中断、外中断 1.3 什么是可屏蔽中断、不可屏蔽中断 2. CM3 内核中断介绍 2.1 F103系统异常清单 2.2 F103 外部中断清单 3. NVIC 简介 3.1 NVIC 寄存器简介 3.2 NVIC 相关寄存器的介绍 4. 中断优先级 4.1 优先…

同义词替换器降低论文重复率的最新技术进展

大家好&#xff0c;今天来聊聊同义词替换器降低论文重复率的最新技术进展&#xff0c;希望能给大家提供一点参考。 以下是针对论文重复率高的情况&#xff0c;提供一些修改建议和技巧&#xff1a; 标题&#xff1a;同义词替换器降低论文重复率的最新技术进展 一、引言 随着学术…

Android : Room 数据库的基本用法 —简单应用_一_入门

1.Room介绍&#xff1a; Android Room 是 Android 官方提供的一个持久性库&#xff0c;用于在 Android 应用程序中管理数据库。它提供了一个简单的 API 层&#xff0c;使得使用 SQLite 数据库变得更加容易和方便。 以下是 Android Room 的主要特点&#xff1a; 对象关系映射…

一个人全干!之后台管理中的搜索区域的展开收缩组件。

后台管理系统中大多数都有列表的搜索&#xff0c;那么用户的需求又需要必要时收缩搜索区域&#xff0c;需要时再展开。 而且怪的是他还需要一些部分不可收缩&#xff0c;不需要的地方才收缩。使用v-if来解决吧又不咋美观&#xff0c;我们还需要一个简单的动画效果。我们先写一…

.NET医院检验系统LIS源码,使用了oracle数据库,保证数据的隔离和安全性

医院检验系统LIS源码&#xff0c;LIS系统全套商业源码 LIS系统实现了实验室人力资源管理、标本管理、日常事务管理、网络管理、检验数据管理&#xff08;采集、传输、处理、输出、发布&#xff09;、报表管理过程的自动化&#xff0c;使实验室的操作人员和管理者从繁杂的手工劳…

vue的computed中的getter和setter

vue的computed中的getter和setter 定义getter写法setter写法 定义 computed 中可以分成 getter&#xff08;读取&#xff09; 和 setter&#xff08;设值&#xff09;&#xff0c;一般情况下是没有 setter 的&#xff0c;computed 预设只有 getter&#xff0c;也就是只能读取&a…

【EI会议征稿】第三届电力系统与电力工程国际学术会议(PSPE 2024)

第三届电力系统与电力工程国际学术会议&#xff08;PSPE 2024&#xff09; 2024 3rd International Conference on Power System and Power Engineering(PSPE 2024) 第三届电力系统与电力工程国际学术会议&#xff08;PSPE 2024&#xff09;于2024年3月29-31日在中国三亚隆重召…

CVPR 2023 三维重建相关必读论文和代码合集

三维重建涉及将二维图像或视频转换为三维模型的过程&#xff0c;这个过程需要应用到多门学科的知识&#xff0c;比如数学、计算机图形学和多视图几何等&#xff0c;学习门槛较高。但尽管如此&#xff0c;三维重建仍然是CV领域的一个热门方向。 目前三维重建技术已经有了广泛应…

Spring Boot 3.x.x Spring Security 6.x.x @PreAuthorize 失效

Spring Boot 3.x.x Spring Security 6.x.x PreAuthorize 失效 背景问题解决备注 背景 最近在搞一个后端项目&#xff0c;登录、接口权限、token认证。 版本 Spring Boot 3.2.0 JDK 21 Spring Security 6.2.0 问题 PreAuthorize 失效&#xff0c;没有走认证。 解决 给PreAu…

金属制造ERP是什么?可以帮助企业解决什么问题

不同的金属有不同的制造工艺和生产工序&#xff0c;有些金属制造企业并不能按照既有的生产计划执行下去&#xff0c;此外有些工艺还可能受到设备或资源等影响造成部分加工流程出现问题&#xff0c;从而导致物料损耗大&#xff0c;产品交期延误等。 另外&#xff0c;有些金属制…

nodejs微信小程序+python+PHP沧州地区空气质量数据分析系统-计算机毕业设计推荐 django

本系统不仅主要实现了注册登录&#xff0c;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;城市区域管理&#xff0c;空气状况管理&#xff0c;空气质量管理&#xff0c;系统管理&#xff0c;数据爬取&#xff0c;大屏分析等功能&#xff0c;通过这些功能基本可…

Android---Kotlin 学习005

substring 字符串截取。相加与 java&#xff0c;kt 里面的 substring 函数支持 IntRange 类型&#xff08;表示一个整数范围的类型&#xff09;的参数&#xff0c;until 创建的范围不包括上限值。 const val NAME "Jimmys friend" fun main(){val index NAME.ind…

fuxploide,一款针对文件上传的Fuzz检测工具

fuxploide,一款针对文件上传的Fuzz检测工具 1.工具概述2.安装3.参数解析4.使用案例1.工具概述 Fuxploider 是一种开源渗透测试工具,可自动检测和利用文件上传表单缺陷。该工具能够检测允许上传的文件类型,并能够检测哪种技术最适合在所需的 Web 服务器上上传 Web Shell 或任…

Xubuntu16.04系统中使用EDIMAX EW-7822UAC无线网卡开启5G自发AP

目录 1.关于 EDIMAX EW-7822UAC2.驱动安装3.查看无线网卡信息3.通过create_ap配置5G自发AP 1.关于 EDIMAX EW-7822UAC 官网介绍 https://www.edimax.com/edimax/merchandise/merchandise_detail/data/edimax/global/wireless_adapters_ac1200_dual-band/ew-7822uac/ 详细参数…

HarmonyOS:NativeWindow 开发指导

场景介绍 NativeWindow 是 HarmonyOS 本地平台化窗口&#xff0c;表示图形队列的生产者端。开发者可以通过 NativeWindow 接口进行申请和提交 Buffer&#xff0c;配置 Buffer 属性信息。 针对 NativeWindow&#xff0c;常见的开发场景如下&#xff1a; ● 通过 NativeWindow…

如何在Android中旋转屏幕时避免重新绘制Activity

如何在Android中旋转屏幕时避免重新绘制Activity 在Android开发中&#xff0c;设备旋转通常导致当前活动&#xff08;Activity&#xff09;被销毁并重新创建&#xff0c;这可能导致用户界面重置和不必要的资源重新加载。然而&#xff0c;有时我们希望避免这种行为&#xff0c;…

飞天使-linux操作的一些技巧与知识点4-ansible常用的技巧,配置等

文章目录 ansible配置文件的优先级尝试开始进行操作ansible常用模块ansible 的playbook示例安装phpplaybook中变量的引用 ansible yum install -y ansible 测试是否可用 ansible localhost -m ping /etc/ansible/ansible.cfg &#xff1a;主配置文件&#xff0c;配置 ansible…