React学习笔记(一)

文章目录

  • [1. React介绍](#1. React介绍)
      • [1.1.1. React是什么?](#1.1.1. React是什么?)
      • [1.1.2. 为什么学?](#1.1.2. 为什么学?)
      • [1.1.3. React特点](#1.1.3. React特点)
  • [2. 创建React元素--三个API](#2. 创建React元素--三个API)
    • [2.1. React.createElement() :创建一个React元素](#2.1. React.createElement() :创建一个React元素)
    • [2.2. ReactDOM.createRoot() :创建React的根容器,用来放置React元素](#2.2. ReactDOM.createRoot() :创建React的根容器,用来放置React元素)
    • [2.3. root.render() :](#2.3. root.render() :)
  • [3. JSX(JS扩展)](#3. JSX(JS扩展))
      • [3.1.1. 基本使用](#3.1.1. 基本使用)
      • [3.1.2. 注意事项:](#3.1.2. 注意事项:)
      • [3.1.3. 渲染列表](#3.1.3. 渲染列表)
  • [4. 虚拟DOM](#4. 虚拟DOM)
  • [5. 创建React项目](#5. 创建React项目)
    • [5.1. 手动操作](#5.1. 手动操作)
    • [5.2. 自动创建](#5.2. 自动创建)
  • [6. 组件](#6. 组件)
    • [6.1. 函数组件](#6.1. 函数组件)
    • [6.2. 类组件](#6.2. 类组件)
    • [6.3. 类组件的状态数据](#6.3. 类组件的状态数据)
    • [6.4. 组件间通信:父传子--props](#6.4. 组件间通信:父传子--props)
  • [7. 事件](#7. 事件)
  • [8. State](#8. State)
    • [8.1. useState( )](#8.1. useState( ))
    • [8.2. 类组件中的State](#8.2. 类组件中的State)
  • [9. Ref()](#9. Ref())
  • [10. 表单](#10. 表单)
    • [10.1. 获取表单数据](#10.1. 获取表单数据)
    • [10.2. 表单的双向绑定(使用useState)](#10.2. 表单的双向绑定(使用useState))
  • [11. 组件生命周期](#11. 组件生命周期)
    • [11.1. 定义](#11.1. 定义)
    • [11.2. 钩子函数](#11.2. 钩子函数)
  • [12. 条件渲染](#12. 条件渲染)
    • [12.1. if渲染](#12.1. if渲染)
    • [12.2. 三目运算符](#12.2. 三目运算符)
    • [12.3. 逻辑与运算符](#12.3. 逻辑与运算符)
  • [13. 列表渲染](#13. 列表渲染)
  • [14. portal](#14. portal)
  • [15. React中的css写法](#15. React中的css写法)
    • [15.1. 内联样式](#15.1. 内联样式)
    • [15.2. 单独的css文件](#15.2. 单独的css文件)
    • [15.3. css moudules](#15.3. css moudules)
    • [15.4. css in js(styled-components)](#15.4. css in js(styled-components))
  • [16. Fragement](#16. Fragement)
  • [17. Context](#17. Context)
  • [18. setState()的执行流程](#18. setState()的执行流程)
  • [19. useEffect](#19. useEffect)
  • [20. useReducer](#20. useReducer)
  • [21. React.memo](#21. React.memo)
  • [22. useCallback()](#22. useCallback())

1. React介绍

1.1.1. React是什么?

是一个将数据渲染为HTML视图的开源JavaScript库

1.1.2. 为什么学?

  1. 原生JS操作DOM繁琐,效率低(DOM-API操作UI
  2. 使用JS直接操作DOM,浏览器会进行大量的重绘重排
  3. 原生JS没有组件化编码方案,代码复用率低

1.1.3. React特点

  1. 采用组件化模式声明式编码,提高开发效率和组件复用率
  2. 在React Native中可以使用React语法进行移动端开发
  3. 使用虚拟DOM 和优秀的Diffing算法,尽量减少与真实DOM的交互

2. 创建React元素--三个API

2.1. React.createElement() :创建一个React元素

参数:

  1. 元素的名称,html标签必须是小写
  2. 标签中的属性
  • class属性需要使用className来代替
  • 在设置事件时,属性名需要修改为驼峰命名
  1. 元素的内容(子元素)

注意:

  1. React元素最终会通过虚拟DOM转换为真实DOM
  2. React元素一旦创建就不能修改,只能通过新创建元素来替换

2.2. ReactDOM.createRoot() :创建React的根容器,用来放置React元素

2.3. root.render() :

  1. 当首次调用时,容器节点里的所有DOM元素都会被替换,后续的调用则会使用

React的DOM差分算法(DOM diffing algorithm)进行高效的更新。

  1. 不会修改容器节点(只会修改容器的子节点)。可以在不覆盖现有子节点的情况下,

将组件插入已有的DOM节点中。

html 复制代码
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- React 核心库 -->
    <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <!-- React DOM 渲染库 -->
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  </head>
  <body>
    <div id="root"></div>
    <script>
      
      //创建一个React元素
      const button = React.createElement(
        "button",
        {
          id: "btn",
          type: "button",
          className: "hello",
          onClick: () => {
            alert("点击了");
          },
        },
        "点击我",
      );
      //获取根元素
      const root = ReactDOM.createRoot(document.getElementById("root"));
      root.render(button);
    </script>
  </body>
</html>

3. JSX(JS扩展)

  1. 是JavaScript的语法扩展,JSX使得我们可以以类似于HTML的形式去使用JS
  2. 是React中声明式编程 的体现方式。声明式编程,简单理解就是以结果为导向的编程。
  3. 使用JSX将我们所期望的网页结构编写出来,然后React再根据JSX自动生成JS代码。
  4. JSX 是React.createElement()的语法糖,我们所编写的JSX代码,最终都会转换为以调用React.createElement()创建元素的代码。
  5. 在React中使用JSX,需要引入babel来完成"翻译"工作

3.1.1. 基本使用

  1. 引入 babel ,因为浏览器本身不认识jsx
  2. script标签加上type="text/babel"
html 复制代码
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- React 核心库 -->
    <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <!-- React DOM 渲染库 -->
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <!-- Babel 用于浏览器端编译 JSX -->
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/babel">
      const div = (
        <div>
          <h1>Hello World</h1>
        </div>
      );
      const root = ReactDOM.createRoot(document.getElementById("root"));
      root.render(div);
    </script>
  </body>
</html>

3.1.2. 注意事项:

  1. JSX不是字符串,不要加引号
  2. JSX中的html标签应该小写,React组件应该大写开头
  3. JSX中有且只有一个跟标签
  4. JSX的标签必须正确结束(自结束标签必须写/
  5. 可以使用{}嵌入表达式(有值的语句就是表达式)
  6. 如果表达式是空值,布尔值,undefined,将不会显示
javascript 复制代码
const myId='atfenghui'
const myData='hello,REACT'
// <!-- 1.创建虚拟DOM -->
const VDOM = (
            <h2 id={myId.toUpperCase()}>
                <span>{myData.toLowerCase()}</span>
            </h2>
  )
  1. 属性可以在标签中直接设置
  • 样式的类名指定不要用class,要用className
  • 内联样式,要用style={``{key.value}}的形式,外面大括号表示里面是一个表达式,里面的大括号表示对象,里面的属性要使用驼峰命名
html 复制代码
<div style={{color:"white",fontSize:"20px"}}>天气真好</div>

3.1.3. 渲染列表

javascript 复制代码
  const arr=['孙悟空','猪八戒','小牛']
  const list=<ul>{arr.map((item,index)=><li key={index}>{item}</li>)}</ul>
  const root = ReactDOM.createRoot(document.getElementById("root"));
  root.render(list);

4. 虚拟DOM

在React我们操作的元素被称为React元素,并不是真正的原生DOM元素,

React通过虚拟DOM将React元素和原生DOM,进行映射,虽然操作的React元素,但是这些操作最终都会在真DOM中体现出来

虚拟DOM的好处:

  1. 降低API复杂度
  2. 解决兼容问题
  3. 提升性能(减少DOM的不必要操作)

​ 每当我们调用root.render()时,页面就会发生重新渲染,React会通过diffing算法,将新的元素和旧的元素进行比较,通过比较找到发生变化的元素,并且**只对变化的元素进行修改,没有发生的变化不予处理,**在控制台中的体现:发生变化的元素会变成紫色

​ 在比较时,先比较父元素,若父元素不一样,直接将该父元素及起所有子元素全部替换;父元素一致,则去逐个比较子元素,直到找到发生变化的元素。

​ 当我们在JSX中显示数组 中,数组中每一个元素都需要设置一个唯一key ,否则控制台会显示红色警告。重新渲染页面时,React会按照顺序依次比较 对应的元素,当渲染一个列表时如果不指定key,同样也会按照顺序进行比较,如果列表的顺序永远不会发生变化,这么做当然没有问题,但是如果列表的顺序会发生变化,这可能会导致性能问题出现。

​ 比如逆序添加时,由于新元素插入到了列表中的第一个位置,其余元素内容不变,只是顺序发生改变,而React默认根据位置比较元素,所以,此时所有元素都会被修改。

​ 为了解决这个问题,React为列表设计了一个key属性,key在页面中无法查看,这样React就会比较key相同的元素,而不是按照顺序比。所设置的key在当前列表中唯一即可,一般使用ID。尽量不使用元素索引index,因为索引会跟着元素位置改变而改变,和没有设置一样。当元素顺序不会发生改变时,可以使用index

5. 创建React项目

5.1. 手动操作

  1. 目录:
latex 复制代码
src
  --index.js
public
  --index.html
  1. 初始化和安装
bash 复制代码
npm init -y
yarn add react react-dom react-scripts
  1. 打包命令,会生成build目录
bash 复制代码
npx react-scripts build
  1. 运行,会实时更改
bash 复制代码
npx react-scripts start
  1. 替换两种复杂的操作:在package.json中添加:
javascript 复制代码
"scripts": {
    "start": "react-script start",
    "build": "react-script build"
  },
运行:npm start
打包:npm run build
  1. 样式:在src下建立index.css文件,和index.js同级,样式写在这里面
css 复制代码
body{
    background-color: #f5aaaa;
}

5.2. 自动创建

bash 复制代码
npx create-react-app

6. 组件

6.1. 函数组件

一、函数组件的定义

  1. 函数组件就是一个函数
  2. 函数名就是组件名
  3. 函数名首字母必须大写【因为组件在调用的时候,是使用jsx语法调用的】
  4. 函数的返回值,就是组件渲染的结果

二、函数组件的调用

  1. <组件名/> 调用该组件,就是执行组件函数
  2. 函数的返回值,就是渲染的结果,替换掉调用标签
javascript 复制代码
const App = () => {
  return (
    <div>
      <h1 className="heading">我是App组件</h1>
    </div>
  );
};
export default App;
//src/index.js :入口文件
import ReactDOM from "react-dom/client";
import './index.css'
import App from './App.js'
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App/>);

6.2. 类组件

一、类组件定义

  1. 类组件使用class进行声明定义
  2. 类组件要继承 React.Component
  3. 类组件的类名就是组件名
  4. 类组件类名首字母必须大写
  5. 类组件,必须有render方法
  6. render方法执行后,必须要有返回值,返回值是什么,调用组件后渲染的内容就是什么。99.999% 返回值都是一个react元素

二、类组件的调用

  1. 组件使用jsx语法进行调用

​ 单标签:<组件名 />

​ 对标签:<组件名></组件名>

  1. 类组件被调用,是如何执行的?
  • 当遇到类组件 时,首先会实例化 该组件 new App()
  • 使用实例化出来的对象,调用 render方法
  • render方法 return 的内容,替换掉 调用标签
javascript 复制代码
import React from 'react'
class App extends React.Component{
   render(){
    return (
      <div>
        <h1 className="heading">我是App 类 组件</h1>
      </div>
    )
   } 
}
export default App;

6.3. 类组件的状态数据

  1. 创建状态数据
javascript 复制代码
constructor(){
    // 因为App继承了 Component,所以首先要调用父类的构造函数
    super()
    // 定义自身的属性
    // state属性是特殊的,状态数据的属性,值一般是一个对象,对象中的属性就是状态数据
    this.state = {
        count:10,
        msg:'atguigu'
    }
    // 构造函数,都是类的实例调用的,所以此处的this永远指向组件的实例对象
}
  1. 读取
  • 直接读取 {this.state.count}
  • 解构之后读取
javascript 复制代码
let {count,msg} = this.state
  1. 改变

通过setState函数

javascript 复制代码
this.setState({
    count:this.state.count + 1
})

6.4. 组件间通信:父传子--props

  1. 传递方式:调用子组件的标签,通过属性传递

    1. 逐个属性单独赋值传递
    2. 通过...扩展运算批量传递
  2. Props是只读的,不能更改

  3. 类组件的接收方式: 子组件中通过 this.props.特殊属性接收

javascript 复制代码
import Child from './components/Child'
export default class App extends Component {
    render() {
        return (
            <div>
                <h3>App组件</h3>
                <hr />
                {/*通过属性传递数据给子组件*/}
                <Child username='atguigu' age={10}/>
            </div>
        )
    }
}
import React, { Component } from 'react'
export default class Child extends Component {
    render() {
        console.log('child: ',this.props)
        let {username, age} = this.props
        return (
            <div>
                <h4>Child</h4>
                <p>props username: {this.props.username} - {username}</p>
                <p>props age: {this.props.age}- {age}</p>
                
            </div>
        )
    }
}
  1. 函数组件接收方式:将props作为参数
javascript 复制代码
import LogItems from "./LogItems";
const Logs = () => {
  return (
    <div className="Logs">
      {/* {父组件向子组件传递数据:props} */}
      <LogItems
        date={new Date(2021, 7, 21, 19, 20)}
        time="2021-09-09"
        desc="学习react"
      />
    </div>
  );
};
export default Logs;
import MyDate from "./MyDate";
const LogItem=(props)=>{
    // props.desc="嘻嘻"//props属性是只读的,不能改
    return(
        <div className="item">
            <MyDate date={props.date}/>
            <div className="log-item-title">{props.desc}</div>
            <div className="log-item-content">{props.time}</div>
        </div>
    )
}
export default LogItem;
import React from "react";
const MyDate = (props) => {
  const month = props.date.toLocaleString("zh-CN", { month: "long" });
  const date = props.date.getDate();
  return (
    <div className="date">
      <div className="month">{month}</div>
      <div className="day">{date}</div>
    </div>
  );
};
export default MyDate;

7. 事件

事件对象

​ React事件中同样会传递事件对象,可以在响应函数中定义参数来接收事件对象

​ React中的事件对象同样不是原生的事件对象,是经过React包装后的事件对象

​ 由于对象进行过包装,所以使用过程中我们无需再去考虑兼容性问题

javascript 复制代码
//1. 函数组件
const App = () => {
  const clickHandler = (event) => {
    event.preventDefault(); //React中取消默认行为(a标签)
    event.stopPropagation();//取消事件冒泡
    alert("点击了按钮");
    //return false;React中无法通过这种方式来取消默认行为
  };
  return (
    <div>
      <h1 className="heading">我是App组件</h1>
      <button onClick={clickHandler}>我是按钮</button>
      <a href="https://www.baidu.com" onClick={clickHandler}>
        我是超链接
      </a>
    </div>
  );
};
export default App;

8. State

​ props中的所有属性都是不可变的,这使得React组件不能随着props的改变而改变。但在实际

的开发中,我们更希望的是数据发生变化时,页面也会随着数据一起变化。React为我们提供了

state用来解决这个问题。

​ state只属于自身组件,其他组件无法访问,是可变的

​ state相当于一个变量,只是这个变量在React中进行了注册,React会监控这个变量的变化,当state发生变化时,会自动触发组件的重新渲染,在函数组件中,我们需要使用钩子函数,获取state

8.1. useState( )

  1. 使用钩子函数useState()来创建state
  2. 需要一个值作为参数,这个值是state的初始值
  3. 该函数会返回一个数组
  • 数组的第一个元素,是初始值,初始值只是用来显示数据,直接修改不会触发组件的重新渲染
  • 数组的第二个元素,是一个函数,通常会命名为setXXX,用来修改state,调用这个函数来修改state,只有stat发生变化时才会触发组件的重新渲染,并不是说只要调用了这个函数就会重新渲染。并且使用函数中的值作为新值。
  • 当state的值是一个对象时,新对象会全部替换旧对象
  • 当通过setState去修改一个state时,并不表示修改当前的state,他修改的是下一次渲染时的state值
  • setState()会触发组件的重新渲染,是异步的,所以当调用setState()需要用旧state的值时,一定要注意有可能出现计算错误的情况(连续修改,但是第一次还没执行就已经执行第二次了,则第二次函数执行时拿到的旧值不是上一次执行后的值),为了避免这种情况,可以通过为setState()传递回调函数的形式来修改state
javascript 复制代码
import React, { useState } from "react";
//1. 函数组件
const App = () => {
  const result = useState(1);
  // let counter=result[0];//1
  const [counter, setCounter] = result;

  const addHandler = () => {
    // setCounter(counter + 1);
    //使用回调函数,React会确保函数的返回值一直是最新的
    // setCounter((prevCounter) => {
    //   return prevCounter + 1;
    // });
    //简写
    setCounter((prevCounter) => prevCounter + 1);
  };
  const lessHandler = () => {
    setCounter(counter - 1);
  };
  const [user, setUser] = useState({ name: "张三", age: 18 });
  const updateHandler = () => {
    console.log("重新渲染");

    //方式一
    // setUser({ name: "张三", age: 19 });//点击修改按钮时,页面会重新渲染,将旧对象替换
    //方式二
    // user.name="lisi"
    // setUser(user);//这是相当于直接修改了就得state对象,由于对象还是那个对象,引用没变,所以不会重新渲染,点击修改按钮时,实际数据已经变了,但是页面不会改变,也没有发生重新渲染。因为只改变了属性,而这个对象的地址没有发生改变
    //方式三:浅复制
    const newUser = Object.assign({}, user);//把user中的值全都复制到新创建的这个空对象里面
    console.log(newUser===user);//false
    newUser.name = "lisi";
    setUser(newUser);
    //方式四(三的简写,其实也是浅复制)
    setUser({ ...user, name: "lisi" });
  };
  return (
    <div>
      <h1 className="heading">{counter}</h1>
      <h1 className="heading">
        {user.name}--{user.age}
      </h1>

      <button onClick={addHandler}>点击+1</button>
      <button onClick={lessHandler}>点击-1</button>
      <button onClick={updateHandler}>点击修改</button>

      <p>{counter}</p>
    </div>
  );
export default App;

8.2. 类组件中的State

  1. 类组件中state统一存储到了实例对象的state属性中,可以通过this.state获取state数据,通过this.setState()来修改,这时React只会修改设置了的属性,不会影响其他的属性,但是这仅限于直接存储于state中的属性,比如代码中obj中的nameage,如果只修改name,则age会丢失
  2. 获取DOM对象
  • 创建一个属性,用来存储DOM对象
javascript 复制代码
divRef = React.createRef();
  • 在render方法中,将ref属性绑定给DOM元素
javascript 复制代码
<div ref={this.divRef}></div>
  1. 函数组件中,响应函数直接以函数的形式定义在组件中的;在类组件中,响应函数是以类的方法来定义的
javascript 复制代码
import React, { Component } from "react";
//类组件的props
class User extends Component {
  state = {
    count: 0,
    test: "test",
    obj: { name: "张三", age: 18 },
  };
  //获取DOM对象
  //1. 创建一个属性,用来存储DOM对象
  divRef = React.createRef();
  //2. 在render方法中,将ref属性绑定给DOM元素
  
  
  //类组件中,用箭头函数,避免this问题
  clickHandler = () => {
    // this.setState({count:this.state.count+1})
    // this.setState({count:10})这里只对count进行了修改,但是原来的数据test属性不会丢失

    // this.setState((prevState)=>{
    //     return {count:prevState.count+1}
    // })
    // this.setState({obj:{name:'沙和尚'}})//这种情况只改变name,而且age会丢失
    this.setState({ obj: { ...this.state.obj, name: "沙和尚" } }); //这样改会改掉name并且会保留age
  };
  render() {
    return (
      <div ref={this.divRef}>
        <h1>{this.state.count}</h1>
        <button onClick={this.clickHandler}>点击</button>
        <ul>
          <li>姓名:{this.props.name}</li>
          <li>年龄:{this.props.age}</li>
          <li>性别:{this.props.gender}</li>
        </ul>
      </div>
    );
  }
}
export default User;

9. Ref()

直接从React处获取DOM对象步骤:

  1. 创建一个存储DOM对象的容器:使用useRef()钩子函数
  2. 将容器设置为想要获取DOM对象的ref属性,React会自动将当前元素的DOM对象,设置为容器current属性

钩子函数的注意事项:

  1. React中的钩子函数只能用于函数组件或自定义钩子
  2. 钩子函数只能直接在函数组件中调用,不能在某个内部函数中调用
javascript 复制代码
import React, {useRef } from "react";
const App = () => {
  const h1Ref = useRef(); //h1Ref相当于一个容器;这种方式创建对象,可以确保每次渲染,
  //所获取到的对象都是同一个对象
  //const h1Ref={current:null},这种方式创建的对象,组件每次重新渲染都会创建一个新对象
  const clickHandler = () => {
    const header = document.getElementById("header");
    h1Ref.current.innerHTML = "嘻嘻";
    console.log(header === h1Ref.current); //true
  };
  return (
    <div>
      //将ref写在h1里面,表示要获取这个容器
      <h1 id="header" ref={h1Ref}>
        我是Ref
      </h1>
      <button onClick={clickHandler}>点击修改header内容</button>
    </div>
  );

export default App;

10. 表单

10.1. 获取表单数据

javascript 复制代码
import React, { useRef } from "react";
import Card from "../UI/Card";
const LogsForm = () => {
  let inputDate = "";
  let inputDesc = "";
  let inputTime = "";
  //监听日期变化
  const dateChangeHandler = (e) => {
    //获取到当前触发事件的对象,时间对象中保存了当前时间触发时的所有信息
    //e.target是出发事件的对象
    inputDate = e.target.value;
  };
  //监听内容变化
  const descChangeHandler = (e) => {
    inputDesc = e.target.value;
  };
  const timeChangeHandler = (e) => {
    inputTime = e.target.value;
  };
  //当表单提交时,要汇总数据
  //在React中表单不需要自行提交,而是要通过React提交
  const submitHandler = (e) => {
    e.preventDefault(); //取消默认提交行为
    //获取数据
    const newLog = {
      data: new Date(inputDate),
      desc: inputDesc,
      time: +inputTime, //+表示将字符串转换为数字
    };
    console.log(newLog);
  };
  return (
    <Card className="logs-form">
      <form onSubmit={submitHandler}>
        <div className="form-item">
          <label htmlFor="date">日期</label>
          <input type="date" id="date" onChange={dateChangeHandler}></input>
        </div>
        <div className="form-item">
          <label htmlFor="desc">内容</label>
          <input
            type="text"
            id="desc"
            onChange={descChangeHandler}
          ></input>
        </div>
        <div className="form-item">
          <label htmlFor="time">时长</label>
          <input type="number" id="time" onChange={timeChangeHandler}></input>
        </div>
        <button>添加</button>
      </form>
    </Card>
  );
};
export default LogsForm;

10.2. 表单的双向绑定(使用useState)

上面的表单属于非受控组件

实现双向绑定的表单是受控组件,如下

将表单中的数据存储到state中

​ 将state设置为表单项的value值,这样当表单项发生改变时,state也会随之变化,反之,state发生变化时,表单项也会跟着改变,这种操作步骤称之为双向绑定,这样一来,表单成为了一个受控组件

javascript 复制代码
import React, { useState } from "react";
import Card from "../UI/Card";
const LogsForm = () => {
  //   const descRef = useRef();
  //   let inputDate = "";
  //   let inputDesc = "";
  //   let inputTime = "";
  const [inputDate, setInputDate] = useState("");
  //监听日期变化
  const dateChangeHandler = (e) => {
    // inputDate = e.target.value;
    setInputDate(e.target.value);
  };
  const submitHandler = (e) => {
    e.preventDefault(); //取消默认提交行为
    //获取数据
    const newLog = {
      date: new Date(inputDate),
      desc: inputDesc,
      time: +inputTime, //+表示将字符串转换为数字
    };
    //清空表单
    setInputDate("");
    setInputDesc("");
    setInputTime("");
    console.log(newLog);
  };
  return (
    <Card className="logs-form">
      <form onSubmit={submitHandler}>
        <div className="form-item">
          <label htmlFor="date">日期</label>
          <input
            type="date"
            id="date"
            onChange={dateChangeHandler}
            value={inputDate}
          ></input>
        </div>
        <button>添加</button>
      </form>
    </Card>
  );
};
export default LogsForm;

11. 组件生命周期

11.1. 定义

  1. 组件的生命周期:组件从被创建到挂载到页面中运行,在到组件不用时卸载的过程
  2. 钩子函数:生命周期的每个阶段总是伴随者一些方法的调用,为开发人员在不同阶段操作组件提供了时机
  3. 只有类组件才有生命周期。

11.2. 钩子函数

  1. 创建时:construct->render->React更新DOM和refs->componentDidMount
javascript 复制代码
// Parent.js
import React from 'react';
class Parent extends React.Component{
    constructor(props){
        super(props)
        this.state = {
            data:0
        }
        console.log('生命周期钩子函数: constructor')
    }
    componentDidMount(){
        console.log('生命周期钩子函数 componentDidMount')
    }
    render(){
        console.log('生命周期钩子函数: render')
        return (
        <div>
            <h1>{this.state.data}</h1>
        </div>
        )

    }
}

export default Parent
  1. 更新时:render(new Props,setState(),forceUpdate())->React更新DOM和refs -> componentDidUpdate
javascript 复制代码
// Parent.js
import React from 'react';
class Parent extends React.Component{
    constructor(props){
        super(props)
        this.state = {
            count:0
        }
        console.log("=============创建时开始===============")
        console.log('生命周期钩子函数 constructor')
    }
    handleClick = ()=>{
        this.setState({
           count:this.state.count + 1
        })
    }
    refreshClick = ()=>{
        this.forceUpdate()
    }
    componentDidMount(){
        console.log('生命周期钩子函数 componentDidMount')
        console.log('=============创建时结束=================')
    }
    componentDidUpdate(){
        console.log("生命周期钩子函数 componentDidUpdate")
        if(this.state.count > 10){
            console.log("已达到最大数目,重置")
            this.setState({
                count:0
            })
        }
        console.log(document.getElementById('title').innerHTML)
        console.log("===============更新完成===================")
    }
    render(){
        console.log("===============渲染开始===================")
        console.log('生命周期钩子函数 render')
        return (
        <div>
            <Counter count={this.state.count}/>
            <button onClick={this.handleClick}>add+1</button>
            <button onClick={this.refreshClick}>刷新页面</button>
        </div>
        )

    }
}

class Counter extends React.Component{
    componentDidUpdate(prevProps){
        console.log("子组件-生命周期钩子函数 componentDidUpdate")
        console.log("上一次的props:",prevProps,",当前的props:",this.props)
        if(prevProps.count !== this.props.count && this.props.count > 10){
            console.log("已达到最大数目,重置")
            this.setState({
                count:0
            })
        }
        console.log("===============子组件-更新完成===================")
    }
    render(){
        console.log("===============子组件-渲染开始===================")
        console.log("子组件-生命周期钩子函数 render")
        return (
            <h1 id='title'>统计的数目:{this.props.count}</h1>
        )
    }
}

export default Parent
  1. 卸载时:componentWillUnmount
javascript 复制代码
// Parent.js
import React from 'react';
class Parent extends React.Component{
    constructor(props){
        super(props)
        this.state = {
            count:0
        }
    }
    handleClick = ()=>{
        this.setState({
           count:this.state.count + 1
        })
    }
    componentDidUpdate(){
        console.log("生命周期钩子函数 componentDidUpdate")
        console.log("===============更新完成===================")
    }
    render(){
        console.log("===============渲染开始===================")
        console.log('生命周期钩子函数 render')
        return (
            <div> 
            {
                this.state.count <= 10?
                (
                    <div>
                        <Counter count={this.state.count}/>
                        <button onClick={this.handleClick}>add+1</button>
                    </div>
                )
                :
                (
                    <div>已达到10</div>
                )
            }
            </div>
        )

    }
}

class Counter extends React.Component{
    componentWillUnmount(){
        console.log("==============子组件-卸载开始=============")
        console.log("当前数目:",this.props.count)
        console.log("==============子组件-卸载完成=============")
    }
    componentDidUpdate(){
        console.log("子组件-生命周期钩子函数 componentDidUpdate")
        console.log("===============子组件-更新完成===================")
    }
    render(){
        console.log("===============子组建渲染开始==============")
        return (
            <h1 id='title'>统计的数目:{this.props.count}</h1>
        )
    }
}

export default Parent

12. 条件渲染

12.1. if渲染

javascript 复制代码
const {isShow} = this.state;
let element;
if(isShow) {
    element = <div>显示内容</div>
}

return (
	<div>
    	{element}
    </div>
)

12.2. 三目运算符

javascript 复制代码
const {isShow} = this.state;

return (
	<div>
    	{isShow ? <div>内容1</div> : <div>内容2</div>}
    </div>
)

12.3. 逻辑与运算符

javascript 复制代码
const {isShow} = this.state;

return (
	<div>
    	{isShow && <div>loading...</div>}
    </div>
)

13. 列表渲染

在 React 中通常使用 filter() 筛选需要渲染的组件和使用 map() 把数组转换成组件数组。

通常要在循环中加入key属性,以进行性能优化。

javascript 复制代码
const {list} = this.state;

return (
	<div>
    	<ul>
        	{list.filter(item => item.age > 18).map(item => <li key={item.id}>{item.name}</li>)}
        </ul>
    </div>
)

14. portal

组件默认会作为父组件的后代渲染到页面中,但是有些情况下,这种方式会带来一些问题,通过portal可以将组件渲染到页面中的指定位置

使用方法:

  1. 在index.html中添加一个新元素

  2. 修改组建的渲染方式:

    1. 通过ReactDOM.creatPortal()作为返回值创建元素
    2. 参数:jsx(修改前return的代码);目标位置
javascript 复制代码
import React from 'react';
import './BackDrop.css';
import ReactDOM from 'react-dom';
//获取backdrop的根元素
const backdropRoot=document.getElementById("backdrop-root");
const BackDrop=(props)=>{
    //将这一部分代码传到backdropRoot根元素中
    return ReactDOM.createPortal(
        <div className="backdrop">
            {props.children}
        </div>,backdropRoot
    )
}
export default BackDrop;
<!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>
    <div id="root"></div>
    <!-- 这个容器专门用来放遮罩层 -->
    <div id="backdrop-root"></div>
</body>
</html>

15. React中的css写法

15.1. 内联样式

  1. 优点
  • 不会产生样式冲突
  • 动态设置
  1. 缺点
  • 驼峰写法
  • 有些样式没提示
  • 大量样式代码臃肿混乱
  • 无法编写如伪类、伪元素的样式等
javascript 复制代码
class App extends PureComponent {
    constructor(props){
        super(props)
        this.state = {
            color: 'red'
        }
    }
    render(){
	    const { color } = this.state
        return (
        	<div>
            	<div style={{fontSize: '18px', color: color}}>App Component</div>
            </div>
        )
    }
}

15.2. 单独的css文件

缺点:都属于全局css,容易造成样式冲突

css 复制代码
/* App.css */
.title {
    font-size: 18px;
    color: red;
}
/* App.jsx */
import './App.css'

class App extends PureComponent {
	render(){
        return (
        	<div>
            	<div className="title">App Component</div>
            </div>
        )
    }
}

15.3. css moudules

css modules并不是react特有的,所有类似于webpack配置环境下都可以使用,react脚手架已经内置了css modules的配置。

注意:.css、.less、.scss后缀的样式文件都需要改成.module.css、.module.less、.module.scss

优点:解决了局部作用域问题

缺点:

  1. 类名不能使用连接符"-",如:.home-title
  2. 所有className都必须要用{ style.className }的形式来编写
css 复制代码
.title {
    font-size: 18px;
    color: red;
}
.footer {
    text-align: center;
}
/* App.jsx */
import appStyle from './App.module.css'

class App extends PureComponent {
    render(){
        return (
        	<div>
            	<div className={appStyle.title}>App Component</div>
                
                <div className={appStyle.footer}>Footer Component</div>
            </div>
        )
    }
}

15.4. css in js(styled-components)

styled-components使用标签模板语法

javascript 复制代码
let a = 5;
let b = 10;

tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);

16. Fragement

React.Fragement:是一个专门用来做父容器的组件,他只会将它里面的子元素直接返回,不会创建任何的多余元素

javascript 复制代码
import React, { useState } from "react";
const App = () => {
  // 原先写法,return中必须有一个根元素,否则报错,但是这样页面中会出现一个不需要的div
  return (
    <div>
      <LogsForm onSaveLog={saveLogHandler} />
      <Logs logsData={logsData}  onDeleteLog={deleteLogHandler} />
    </div>
  );
  // 使用React.Fragement
   return (
    <React.Fragement>
      <LogsForm onSaveLog={saveLogHandler} />
      <Logs logsData={logsData}  onDeleteLog={deleteLogHandler} />
    </React.Fragement>
  );
  //语法糖:使用空标签
   return (
    <>
      <LogsForm onSaveLog={saveLogHandler} />
      <Logs logsData={logsData}  onDeleteLog={deleteLogHandler} />
    </>
  );
};
export default App;

17. Context

相当于一个公共的存储空间,我们可以将多个组件都需要访问的数据统一存储到一个Context中,这样就无需通过props逐层传递,即可使组件访问到这些数据

创建:

javascript 复制代码
import React from"react";
const TextContext=React.createContext({
    name:'fff',
    age:18
})
export default TextContext;

使用方式一:

  1. 引入Context
  2. 使用Xxx.Consumer组件来创建元素,Consumer的表歉意需要一个回调函数,他会讲context设置为回调函数的参数,通过参数可以访问到context中存储的数据
javascript 复制代码
import React from "react";
import TextContext from "../store/textContext";
const A=()=>{
    return(
        <TextContext.Consumer>
            {
                (ctx)=>{
                    return <div>{ctx.name},{ctx.age}</div>

                }
            }
        </TextContext.Consumer>
    )
}

使用方式二:使用钩子函数

  1. 引入Context
  2. 使用钩子函数useContext()获取到context,useCOntext()需要一个Context作为参数,他会讲Context中的数据获取并作为返回值返回
javascript 复制代码
import React, { useContext } from "react";
import TextContext from "../store/textContext";
const B=()=>{
    const ctx=useContext(TextContext)
    return(
        <div>{ctx.name},{ctx.age}</div>
    )
}

数据应该在App.js中写

Xxx.Provider表示数据的生产者,可以使用它来指定Context中的数据

通过value来指定Context中存储的数据,这样在该组件的所有子组件中都可以通过Context来访问数据。

​ 会读取理他最近的Provider中的数据,如果没有,就读取Context中的默认数据

javascript 复制代码
import TestContext from "./store/testContext";
import A from "./components/A";
import B from "./components/B";
const App=()=>{
   return (
    <div>
      <TestContext.Provider value={{ name: "Alice", age: 25 }}>
         <A />
         <TestContext.Provider value={{ name: "Alice", age: 25 }}>
             <B />
             <Meals mealsData={mealsData} />
         </TestContext.Provider>
     </TestContext.Provider>
    </div>
  );
}

18. setState()的执行流程

写项目时,如果要使用setState()来修改某个变量,不可以直接在函数体中书写setCount(1);

javascript 复制代码
import React from "react";
// 1. 函数组件
const App = () => {
  const [count, setCount] = React.useState(0);
  console.log("App组件重新渲染了");
  // setCount(1);
  //会报错 Too many re-renders
  //正确写法
  const clickHandler=()=>{
  setCount(1)
  }
  return (
    <div>
      <h1 className="heading" onClick={clickHandler}>{count}</h1>
    </div>
  );
};
export default App;

通过前面的学习,我们知道,当新的state值和旧值相同时,他是不会触发组件的重新渲染的

但是 setState()的执行流程(函数组件):

​ 底层:dispatchSetDate(),会先判断组件当前处于什么阶段,

如果是渲染阶段,不会检查state值是否相同;

如果是非渲染阶段,会检查state的值是否相同,如果值不同,会对组件进行重新渲染

如果值相同,React在一些情况下会继续执行当前组件的渲染,但是这个渲染不会触发其子组件的渲染,这次渲染不会产生实际效果,这种情况通常发生在值第一次相同时

19. useEffect

useEffect()是一个钩子函数,需要一个函数作为参数,这个作为参数的函数,将会在组件渲染完毕后执行;第二个参数是一个数组,在数组中可以指定Effect的依赖项,只有当依赖项发生变化时,Effect才会触发。通常会将Effect中使用的所有变量都设置为依赖项

复制代码
像`useState()`,是由钩子函数`useState()`生成的,`useState()`会确保组件的每次渲染都会获取到相同的`setState()`对象

​ 如果依赖项设置了一个空数组,那么Effect只会在组件初次渲染时执行一次

​ 在开发中,可以将那些会产生副作用的代码编写到useEffect的回调函数中,可以避免这些代码影响到组件的渲染

javascript 复制代码
// 1. 函数组件
const App = () => {
  const [count, setCount] = React.useState(0);
  console.log("App组件重新渲染了");
  // setCount(1);
  // Too many re-renders

  useEffect(() => {
    setCount(1);
  },count);

  return (
    <div>
      <h1 className="heading" onClick={clickHandler}>
        {count}
      </h1>
    </div>
  );
};
export default App;
useEffect(()=>{
    if(ctx.totalNum === 0){
      setShowDetails(false);
      setShowCheckout(false);
    }
  },[ctx,setShowDetails,setShowCheckout])

20. useReducer

useReducer可以向组件中添加一个reducer,用法和redux很像,但却不能用于共享数据,它可以在一些场景下替代useState。

useReducer有三个参数:

  1. reducer整合函数:
  • 对于我们当前state的所有操作都应该在该函数中定义,该函数的返回值,会成为state的新值,如果没有返回值,则默认返回undefined,他在页面中不显示。

  • reducer在执行时,会收到两个参数

    • state:当前最新的state
    • action:action需要一个对象,在对象中会存储dispatch所发送的命令。
  1. initialArg:state的初始值,
  2. init函数:用于计算初始值的函数,可以不传,使用init(initialArg)的执行结果作为初始值,不传时直接使用initialArg

返回值是一个数组:

  1. 第一个参数:state,用来获取state的值
  2. 第二个参数,state修改的派发器,通过派发器可以发送操作state的命令,具体的修改行为将会由另外一个函数(reducer)执行

使用方法:

javascript 复制代码
import React, { useReducer } from "react";
//为了避免reducer会重复创建,会把他定义在组件外面
const countReducer=(state, action) => {
    //可以根据action中的不同type来执行不同的操作
    // if (action.type === "ADD") {
    //   return state + 1;
    // } else if (action.type === "SUB") {
    //   return state - 1;
    // }
    switch (action.type) {
      case "ADD":
        return state + 1;
      case "SUB":
        return state - 1;
      default:
        return state;
    }
    //这一步是防止传错而返回undefined,如果不符合上面两个if则还返回原来的数据
    return state;
  }
const App = () => {
  const [count, countDispatch] = useReducer(countReducer, 1);
  const addHandler = () => {
    countDispatch({ type: "ADD" });
  };
  const subHandler = () => {
    countDispatch({ type: "SUB" });
  };
  return (
    <div>
      <button className="heading" onClick={addHandler}>
        增加
      </button>
      {count}
      <button className="heading" onClick={subHandler}>
        减少
      </button>
    </div>
  );
};
export default App;

21. React.memo

App组件的子组件是A组件,A组件的子组件是B组件,当App组件中有setState语句,并且会触发App组件的重新渲染时,A和B组件家也会跟着重新渲染,但是这两个组件并没有发生任何变化,也不会产生虚拟dom,重新渲染的话很没必要,所以可以使用React.memo()来避免组件的重新渲染

React.memo()是一个高阶组件,会接受另一个组件作为参数,并且会返回一个包装过的新组建。包装过的新组建就会具有缓存功能,包装过后,只有组件的props发生变化才会触发组件的重新渲染,否则总是返回缓存中的结果。

javascript 复制代码
const A=()=>{
  return (
    <div>我是A组件<div/>
  )
}
export default React.memo(A)

22. useCallback()

useCallback()是一个钩子函数,用来创建React中的回调函数,他创建的回调函数不会总在组件重新显然是重新创建。

第一个参数是一个函数,第二个参数是依赖项,只有依赖项发生变化时,回调函数才会重新创建,如果不指定依赖数组,那么回调函数每次都会重新创建。如果是空的依赖数组,那么组件只会在初始化时创建。一定要讲函数当中用到的所有变量添加到依赖项,除了setState,和useEffect很像

复制代码
const clickHandler=useCallback(()=>{  setCount(prevState=>prevState+num);  setNum(prevState=>num+1) },[num])
相关推荐
我想我不够好。2 小时前
4.27消防监控学习 1.30min
学习
坚持不懈的大白2 小时前
并查集知识点学习
学习
YaBingSec2 小时前
玄机网络安全靶场:JBoss 5.x_6.x 反序列化漏洞(CVE-2017-12149)
android·网络·笔记·安全·web安全·ssh
做cv的小昊2 小时前
【TJU】研究生应用统计学课程笔记(5)——第二章 参数估计(2.3 C-R不等式)
c语言·笔记·线性代数·机器学习·数学建模·r语言·概率论
AI360labs_atyun2 小时前
GPT-5.5 和 DeepSeek V4同期发布,谁更行?
人工智能·gpt·学习·ai·agi
其实防守也摸鱼2 小时前
MarkText:开源免费的 Markdown 编辑器新星
笔记·pdf·编辑器·免费·工具·调试·可下载
Ting.~2 小时前
软件设计师备考笔记【day2】-UML 图解 | 面向对象 | 设计模式
笔记·设计模式·uml
承渊政道2 小时前
【动态规划算法】(简单多状态dp问题入门与经典题型解析)
数据结构·c++·学习·算法·leetcode·macos·动态规划
南境十里·墨染春水2 小时前
C++笔记——STL map
开发语言·c++·笔记