一篇文章带你学习react

1、新建react项目

1-1、前期准备

(1)首先确保已经安装node.js

需要在本地开发计算机上安装 Node 8.16.0 或 Node 10.16.0 或更高版本(但服务器上不需要)

(2)下载create-react-app

全局安装:npm install -g create-react-app

如果之前已经下载create-react-app可以先卸载再安装最新版本

1-2、创建react项目

# 查看自己的 npm 版本
npm --version

# 第一种新建项目方式——npm 5.2+ 时,以下命令会安装最新版 CRA
 npx create-react-app my-app


# 第一种新建项目方式——npm 版本小于等于 5.1 时
 create-react-app my-app

# 第二种新建项目方式
npm 6+ 开始支持 npm init <initializer> 
npm init react-app my-app

# 第三种新建项目方式
yarn create react-app my-app

例子:npx create-react-app my-app
出现这样表示创建项目成功

cd my-app
npm start

1-3、目录结构

+ node_module ------ 第三方资源
+ public ------ 静态资源文件夹
    + favicon.ico ------     网站页面图标
    + index.html ------ 主页面
    + logo192.png ------ logo 图
    + logo512.png ------ logo 图
    + manifest.json ------ 应用加壳的配置文件
    + robots.txt ------ 爬虫协议文件
+ src ------ 源码文件夹
    + App.css ------ App 组件的样式
    + App.js ------ App 组件
    + App.test.js ------ 用于给 App 组件做测试
    + index.css ------ 全局样式
    + index.js ------ 入口文件
    + logo.svg ------ logo 图
    + reportWebVitals.js ------ 页面性能分析文件(需要 web-vitals 库的支持)
    + setupTests.js ------ 组件单元测试的文件(需要 jest-dom 库的支持)
 

1-4、文件说明

(1)public/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <!-- 开启理想窗口,用于做移动端网页的适配 -->
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <!-- 用于配置浏览器页签+地址栏的颜色(仅支持安卓手机浏览器,兼容性较差) -->
    <meta name="theme-color" content="#000000" />
    <meta name="description" content="Web site created using create-react-app"/>
    <!-- 用于指定网页添加到手机主屏幕后到图标(仅支持 apple 手机) -->
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <!-- 应用加壳时到配置 -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

(2)src/index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(document.getElementById('root'));
// React.StrictMode 标签会自动校验 react 语法,遇到一些将要遗弃或不推荐使用的语法,会提示
// 不加 React.StrictMode 标签,直接使用 App 组件也没啥影响
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
reportWebVitals();

注意:旧版本的 src/index.js 中,渲染组件是通过方法ReactDOM.render(<App/>,el)实现的:

import React from "react";
import App from "./App"
// 旧版本引入 ReactDOM ,然后 执行 ReactDOM.render()
import ReactDOM from "react-dom"
ReactDOM.render(<App />, document.getElementById("root"))

而新版本18.0.2是通过ReactDOM.createRoot(el).render(<App/>)实现的:

import React from "react";
import App from "./App"
// 新版本引入方式,利用 ReactDOM.createRoot() 创建节点,然后执行 render 函数
import ReactDOM from "react-dom/client"
ReactDOM.createRoot(document.getElementById("root"))
  .render(
    <App/>
  )

(3)ReactDom是一个第三方的模块

位置:index.js;这个文件是整个项目的入口
作用:可以帮助我们把一个组件挂载到某一个dom节点上
以下是将App这个组件挂载到id等于root的节点上进行显示


2、开发

2-1、组件命名

React通过组件的思想,将界面拆分成一个个可复用的模块,每一个模块就是一个React 组件。一个React 应用由若干组件组合而成,一个复杂组件也可以由若干简单组件组合而成。

React 组件可以用好几种方式声明,可以是一个包含 render() 方法的类,也可以是一个简单的函数,不管怎么样,它都是以props作为输入,返回 React 元素作为输出。

组件可以以js为后缀,也可以以 jsx 为后缀,以 jsx 为后缀可以明显区别于其他功能性的 js 文件。

2-2、引入 React 和 Component

(1)只引 React,定义类组件时使用 React.Component

import React from "react";
// 定义类式组件
export default class Hello extends React.Component {
  render() {
    return (
      <h1 className={hello.title}>Hello 组件</h1>
    )
  }
}

(2)解构引入 Component,定义类组件时直接使用 Component

// React 中使用了默认暴露和分别暴露,所以可以使用下面的引入方式
// import React, { Component } from "react";
import { Component } from "react";
// 定义类式组件
export default class Welcome extends Component {
  render() {
    return (
      <h1 className="title">Welcome组件</h1>
    )
  }
}

能使用以上引用方式是因为 React 中使用了 默认暴露 和 分别暴露

class React {
}
// 分别暴露 Component
export class Component {
}
React.Component = Component
// 默认暴露 React
export default React
--------------------------------------
// 其他文件引用时可以这样:
import React, { Component } from "react";
import { Component } from "react";

2-3、创建组件

(1)函数式组件

//组件名称总是以大写字母开始。
//定义模版的函数名首字母必须大写
function Welcome(props){
  //添加其他事件函数这么加:function otherFun(){·····}
  return <div>my name is {props.name},{props.age}</div>;

}

(2)类组件

//组件名首字母必须大写
class Welcome extends React.Component{
  //添加其他事件函数这么加:myWay(){···}
  render(){
    return (<div>my name is {this.props.name},{this.props.age}</div>);
  }
};

(3)React.createClass()

/*组件首字母必须大写,eg:Greeting的G*/
var Greeting = React.createClass({
  render: function() {
    return <h1>Hello, {this.props.name}</h1>;
  }
});

2-4、引入 ReactDOM

(1)新版本18.0.2

要从 react-dom/client中引入 ReactDOM,用法如下:

import React from "react";
// 新版本引入 ReactDOM,渲染节点时使用 ReactDOM.createRoo(el).render(<App/>)
import ReactDOM from "react-dom/client"
import App from "./App";
ReactDOM.createRoot(document.getElementById("root"))
  .render(<App/>)

(2)旧版本中

要从 react-dom 中引入 ReactDOM,用法如下:

import React from "react";
import ReactDOM from "react-dom"
import App from "./App";
ReactDOM.render(<App />, document.getElementById("root"))

2-5、渲染一个组件

(1)js函数式组件

function Welcome(props){
  return <div>my name is {props.name},{props.age}</div>;
}
const element = (
  <Welcome name='bty' age='12'/>
);
ReactDOM.render(
  element,
  document.getElementById('root')
);

输出:my name is bty,12

(2)类组件

//组件名首字母必须大写
class Welcome extends React.Component{
  render(){
    return (<div>my name is {this.props.name},{this.props.age}</div>);
  }
};
//const element必须在定义组件之后
const element = (
  <Welcome name='bty' age="12"/>
);
ReactDOM.render(
  element,
  document.getElementById('root')
);

输出:my name is bty,12

(3)工作步骤:


1、调用ReactDOM.render(),并向其中传入< Welcome name=’bty’ age=’12’/>元素
2、React 调用 Welcome 组件,并向其中传入了 {name: ‘bty’,age=’12’} 作为 props 对象
3、Welcome 组件返回 < div>my name is {props.name},{props.age}
4、 React DOM 迅速更新 DOM ,使其显示为 < div>my name is bty,12

2-6、组件嵌套

function Myhi(props){
   return <h1>I‘m {props.name}</h1>
}
class Welcome extends React.Component{
  render(){
    return(
      <div>
        <p>start</p>
        <Myhi name="bty"/>
        <Myhi name="ssh"/>
        <Myhi name="mama"/>
      </div>)    
  }
};
ReactDOM.render(
  <Welcome/>,
  document.getElementById('root')
);

输出:
start
I‘m bty
I‘m ssh
I‘m mama

组件嵌套是如何渲染的?
拿上面例子说,
(1)首先调用ReactDOM.render(),向其中传入< Welcome/>组件;
(2)渲染时:组件< Welcome/>知道如何渲染< p>标签,但不知道如何渲染< Myhi/>,所以再继续调用解析对应组件< Myhi/>,并以{name:'···'}作为props对象传给Myhi组件,返回React 元素,
(3)像上面这样一直做递归,直到返回的React 元素中只包含DOM节点为止。

这样React 就能获取到页面的完整DOM结构信息,渲染的工作自然就ok了

2-7、组件的实例

React 组件是一个函数或类,实际工作时,发挥作用的是React 组件的实例对象。只有组件实例化后,每一个组件实例才有了自己的props和state,才持有对它的DOM节点和子组件实例的引用。

在传统的面向对象的开发方式中,实例化的工作是由开发者自己手动完成的,但在React中,组件的实例化工作是由React自动完成的,组件实例也是直接由React管理的。换句话说,开发者完全不必关心组件实例的创建、更新和销毁。

在类组件中 的实例,就是用 this 引用的那个对象,对于保存本地状态以及介入生命周期函数是有用的。

注:组件必须是 纯函数

无论你用函数或类的方法来声明组件, 它都无法修改其自身 props. 思考下列 sum (求和)函数:

function sum(a, b) {
  return a + b;
}

这种函数称为 “纯函数” ,因为它们不会试图改变它们的输入,并且对于同样的输入,始终可以得到相同的结果。

反之, 以下是非纯函数, 因为它改变了自身的输入值:

function withdraw(account, amount) {
  account.total -= amount;
}

严格的规则:
所有 React 组件都必须是纯函数,并禁止修改其自身 props

2-8、css 模块化

按照截图方式在同一个组件中引用多个组件,如果 Hello 和 Welcome 组件存在相同类的不同样式时,后者会覆盖前者,所以需要模块化样式,使其互不影响。

怎么做?

(1)将 .css 文件改为 .module.css 文件
(2)引入 css 文件时,使用 import hello from "./Hello.module.css" 代替 import "./Hello.module.css"
组件标签中使用 hello.title, <h1 className={hello.title}>Hello 组件</h1>
编译出来是如下效果:

2-9、组件通信

react的props、state、ref

参考文章:React03-props 和 state 详解_react state和props-CSDN博客

如果是在App.js引入则是全局引入,不用没个组件都引入

(1)父组件=》子组件通信props

子组件是类组件,新建文件ChildByProps1.js,代码如下:

import React,{Component} from "react";
export default class ChildByProps1 extends  Component {
    constructor(props){
        super(props);
    }
    render(){
        return (
            <span>子组件1:{this.props.txt}</span>
        )
    }
}

子组件是函数组件,新建文件ChildByProps2.js,代码如下:

// 例子1
export default function ChildByProps2(props){
    const {txt} = props;
    return <span>子组件2:{txt}</span>
}

//例子2
export default function ChildByProps2(props){
    return <span>子组件2:{props.txt}</span>
}

父组件:

import React from 'react';
import './App.css';
import ChildByProps1 from './Component/ClassComponent/Parent2ChildByProps';
import ChildByProps2 from './Component/FunctionComponent/Parent2ChildByProps';
function App() {
	return (
		<div className='container'>
	      <p>父传子Props</p>
	        <ChildByProps1 txt={"类组件"}></ChildByProps1>
	        <ChildByProps2 txt="函数组件"></ChildByProps2>
	     </div>
	)
}
export default App;

(2)父组件使用refs来直接调用子组件实例的方法

useImperativeHandle可以让你在使用ref时自定义暴露给父组件的实例值

在大多数情况下,应当避免使用ref这样的命令式代码

useImperativeHandle应当与forwardRef一起使用

  • useImperativeHandle使用简单总结:
    • 作用:减少暴露给父组件获取的DOM元素属性, 只暴露给父组件需要用到的DOM方法
    • 参数1:父组件传递的ref属性
    • 参数2: 返回一个对象, 以供给父组件中通过ref.current调用该对象中的方法

函数组件(useRef)
useImperativeHandle、forwardRef、useRef()

1、函数式

函数子组件:

import React, { useState,useImperativeHandle, forwardRef } from "react";
import {message } from "antd";


function ChildByRef2({},ref){
    const showMsg = (msg)=>{
        message.info(msg);
    }
    //将方法暴露给父组件使用
    //@param: ref:父组件传递的ref属性,参数2返回一个对象,以供父组件通过ref.current调用对象中的方法
    useImperativeHandle(ref,()=>({
         show: showMsg
    }));
    return (
        <></>
    )
}
export default forwardRef(ChildByRef2);

函数父组件:

import ChildByRef2 from './Component/FunctionComponent/Parent2ChildByRef';
function App() {
  const myRef2 = useRef();
    //使用子组件暴露的方法
  const myclick2 = ()=>{
     myRef2.current.show("我是函数组件"); 
  }
  return(
  	<div className='container'>
        <ChildByRef2 ref={myRef2}></ChildByRef2>
        <Button onClick={myclick2}>函数父调用函数子组件的方法</Button>
    </div>
  )
}

显示:

2、类组件

类子组件:

import { message } from "antd";
import React from "react";

export default class ChildByRef1 extends React.Component{
    constructor(props){
        super(props)
    }
    show = (msg)=>{
        message.info(msg)
    }
    render() {
        return (
            <></>
        )
    }
}

类父组件:

import { Button } from "antd";
import React,{ Component, createRef } from "react";
import ChildByRef1 from "../Parent2ChildByRef";

export default class Parent1 extends Component{
    constructor(props) {
        super(props)
        this.report = React.createRef();
        this.state={}
    }

    render(){
        return(
            <div>
                <Button onClick={()=>{this.report.current.show("子组件是类组件")}}>
                  父组件是类组件
                </Button>
                <ChildByRef1 ref={this.report}></ChildByRef1>
            </div>
        )
    }
}

显示:

(3)子组件=》父组件callback回调函数

函数子组件

import React from "react";
import { Button } from "antd";

export default function ChildByCallback2({myClick}){
    const handleClick = ()=>{
        myClick("我是函数子组件")
    }
    return (
        <Button type="primary" onClick={handleClick}>click me</Button>
    )
}

类子组件

import { Button } from "antd";
import React from "react";

export default class ChildByCallback1 extends React.Component{
    constructor(props){
        super(props);
    }
    handleClick = ()=>{
        this.props.myClick("我是类子组件")
    }
    render(){
        return (
            <Button type="primary" style={{marginTop:"5px"}} 
                onClick={this.handleClick}>点我</Button>
        )
    }
}

App.js

import ChildByCallback2 from './Component/FunctionComponent/Child2ParentByCallBack';
import ChildByCallback1 from './Component/ClassComponent/Child2ParentByCallBack';
function App() {
	const handleClick = (data)=>{
    	message.success(data)
    }
  	return (
		<div>
			<Divider>子传父Callback</Divider>
        	<ChildByCallback2 myClick={handleClick}></ChildByCallback2>
        	<ChildByCallback1 myClick={handleClick}></ChildByCallback1>
		</div>
	)
}

(4)兄弟组件通信

利用共同父组件(状态提升:将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态)。为了方便,这里使用了钩子函数useState。

兄弟A组件

import React from "react";
export default function SiblingA({count}){
    return (
        <span>A的count值{count}</span>
    )
}

兄弟B组件

import React from "react";
import { Button } from "antd";

export default function SiblingB({updateFunc}){
    const clickFunc = ()=>{
        updateFunc(value=>{
            return value+1
        })
    }
    return (
        <Button onClick={clickFunc}>B修改A的值</Button>
    )
}

App.js

import SiblingA from './Component/FunctionComponent/SiBlingA';
import SiblingB from './Component/FunctionComponent/SiblingB';

function App(){
    const [count,countUpdate] = useState(0)
	return (
		<div>
		    <Divider>兄弟组件利用共同父组件(状态提升)</Divider>
	        <SiblingA count={count}></SiblingA>
	        <SiblingB updateFunc={countUpdate}></SiblingB>
		</div>
	)
}

(5)跨级组件通信

Context

1、调用 React. createContext() 创建 Provider(提供数据) 和 Consumer(消费数据) 两个组件。
2、使用 Provider 组件作为父节点。
3、设置 value 属性,表示要传递的数据。
4、调用 Consumer 组件接收数据。

为了方便,先新建一个文件构建Context对象

import React from "react";

const MyContext = React.createContext();
export default MyContext;

父组件

import React from "react";
import Son from "../Son";
export default function Father(){
    return (
        <div>
            父组件
            <Son></Son>
        </div>
    )
}

子组件

import React from "react";
import Sun from "../Sun";

export default function Son(){
    return (
        <div>
            子组件
            <Sun></Sun>
        </div>
    )
}

孙组件

import React from "react";
import MyContext from "../../../utils/MyContext";

export default function Sun(){

    return (
        <MyContext.Consumer>
            {
                ({color,changeColor})=>
                   <div style={{color:color}}>
                    子孙组件
                    <button onClick={changeColor}>换颜色</button>
                    </div>
            }
            
        </MyContext.Consumer>
    )
}

App.js

import Father from './Component/FunctionComponent/Father';
import MyContext from './utils/MyContext';

function App() {
  const [color,colorUpdate] = useState("green");
	return(
		<div>
      		<Divider>跨级组件Context</Divider>
        	<MyContext.Provider value={{color,
        	changeColor:()=>{if(color!=="blue"){colorUpdate("blue")}else{colorUpdate("green")}}}}>
            	APP组件
            	<Father></Father>
        	</MyContext.Provider>
		</div>
	)
}

(6)全局变量

子组件

import React from "react";
export default class ComponentByGlobal extends React.Component {
    state = {
        value:window.a
    }
    constructor(props){
        super(props)
    }
    componentDidUpdate(prevState){
        if(prevState.value!==this.state.value){
            console.log("window.a is changed to " + this.state.value);
        }
    }
    
    render(){
        return (
        <div>
            window.a的值为{this.state.value}
            <button onClick={()=>{this.setState({value:window.a})}}>点击更新</button>
        </div>
        )
    }
} 

App.js

import ComponentByGlobal from './Component/ClassComponent/GlobalVariable';
function App(){
  window.a=1
  return (
	<div>
      <Divider>全局变量Global Variables</Divider>
        <button onClick={()=>{window.a+=10; console.log(window.a)}}>更改全局变量window.a</button>
        <ComponentByGlobal></ComponentByGlobal>
	</div>
  )
}

(7)props.children

调用组件时,填充在组件标签中的内容

function Person(props) {
  return (
    <div>
      <h3>hello world</h3>
      {props.children}
    </div>
  );
}
 
export default function () {
  return (
    <Person>
      <h3>hello web</h3>
      <h3>hello</h3>
    </Person>
  );
}

2-10、路由

(1)路由表配置:参数地址栏显示

<Route path="/list/:id" component={List} />
html:<Link to='/list/2' >跳转列表页面</Link>
Js: this.props.history.push('/list/2');
List页面接收:
console.log(this.props.match.params.id)//传递过来的所有参数

(2)query方法:参数地址栏不显示,刷新地址栏,参数丢失

html:
<Link to={{ pathname: '/list', query: { name: 'xlf' } }}>跳转列表页面</Link>
Js方式:this.props.history.push({ pathname: '/list', query: { name: ' sunny' } })
List页面接收:
console.log(this.props.location.query.name)//传递过来的所有参数

(3)state方法:参数地址栏不显示,刷新地址栏,参数不丢失,但是该方法只支持BrowerRouter,不支持HashRouter

Html: <Link to={{ pathname: '/list', state: { name: 'xlf' } }}>跳转列表页面</Link>
js:this.props.history.push({ pathname: '/list', state: { name: 'sunny' } })
List页面接收
console.log(this.props.location.state.name)//传递过来的所有参数

参考文章:

8种React组件间通信方式(类组件+函数组件)_react组件通信-CSDN博客

react路由跳转、传参3中方式及区别_react的history.push传参-CSDN博客

3、一些补充

3-1、antd使用

1、在项目目录下下载antd

npm install antd --save

2、在你的 React 组件中引入 Ant Design 的组件

import { Button } from 'antd';

3-2、react 关闭eslint

在package.json中,将eslintConfig中的“react-app”删除即可
(如果无效,将"react-app/jest"一并删除)

"eslintConfig": {
    "extends": [
      "react-app",      // 删除这个
      "react-app/jest"  // 上面的删除后,如果无效,也可以把这个删掉
    ]
  },
 

3-3、什么时候用类组件什么时候用函数组件

函数组件: 这些是简单的JavaScript函数,接受props作为输入并返回JSX元素。它们通常用于表示或无状态组件。

类组件: 这些是继承自React.Component或React.PureComponent的ES6类。它们有一个render()方法,您可以在其中使用JSX定义组件的UI结构。类组件用于需要管理状态或具有生命周期方法的组件。

随着React Hooks的引入,函数组件获得了使用状态和生命周期方法的能力,使得函数组件与类组件之间的区别显著减少。但是,在React应用程序中选择适当的组件类型仍然非常普遍。

以下是一些指导原则:

  1. 简单的 UI 组件:对于简单的 UI 组件,如按钮、图标、展示静态内容的组件等,使用函数组件通常更简洁明了。

  2. 状态管理:如果组件需要内部状态或生命周期方法,则类组件可能更合适。但是,自 React 16.8 版本引入 Hooks 后,函数组件也可以使用状态和其他 React 特性,因此你可以考虑在函数组件中使用 Hooks 来管理状态。

  3. 性能:通常来说,函数组件比类组件具有更好的性能,因为函数组件本身没有实例化过程,并且函数本身具有更轻量的内存开销。但是在实践中,这种性能差异可能不太明显,除非你的应用程序具有非常大量的组件。

  4. 团队约定:有时候,团队可能会有自己的偏好或约定,比如全面采用函数组件或类组件。

至于为什么 Ant Design (antd) 的示例代码大多数都是函数组件,可能有几个原因:

  • 简洁性:函数组件相对于类组件来说,代码量更少,更易于阅读和编写。
  • 性能:函数组件通常比类组件性能更好。
  • Hooks 支持:antd 可能更倾向于使用函数组件,因为函数组件更容易与 React Hooks 配合使用,而 Hooks 是 React 生态系统的一部分,也是未来发展的方向。
  • 函数式编程范式:函数组件符合函数式编程的范式,这与 React 的设计理念有一定的契合。

总的来说,选择函数组件还是类组件取决于你的具体情况和偏好。在现代 React 应用程序中,函数组件在很多情况下是首选,但有些情况下类组件仍然是必需的,比如需要使用生命周期方法或是需要继承 React.Component 的情况。

参考文章:

https://www.cnblogs.com/ygyy/p/18191538

在React中的函数组件和类组件——附带示例的对比_react使用函数还是类组件更好-CSDN博客

3-4、react生命周期

react生命周期可以分为三个阶段

1、挂载阶段

当组件实例被创建并挂载到DOM中时,会执行这些生命周期方法

(1)constructor(props)
  • 构造函数,在创建组件时调用,用于初始化状态和绑定事件处理方法。
  • 在构造函数中通常做两件事情:初始化组件的状态和绑定事件处理方法。
  • 如果不需要初始化state或绑定方法,则不需要显式定义构造函数。
(2)static getDerivedStateFromProps(props, state)
  • 静态方法,用于根据props更新state。
  • 在组件实例化时和接收新props时调用,返回一个对象来更新当前state,如果不需要更新可以返回null。
  • 在React 16.4版本及以上,setState和forceUpdate也会触发该生命周期方法。
(3)render()
  • 渲染方法,准备渲染组件的UI结构。
  • 根据状态state和属性props渲染组件,返回需要渲染的内容。
  • 只做一件事情:返回需要渲染的内容,不要在这个方法内部做其他业务逻辑。
(4)componentDidMount()
  • 在组件第一次渲染完成后调用,通常用于执行一次性操作,如数据获取、订阅事件等。
  • 可以执行依赖于DOM的操作、发送网络请求、添加订阅消息等。
  • 避免在componentDidMount中直接调用setState,因为会触发额外的渲染。
import React, { Component } from 'react';

class SampleComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      counter: 0,
      preCounter: 0
    };
  }

  static getDerivedStateFromProps(props, state) {
    if (props.counter !== state.preCounter) {
      return {
        counter: props.counter,
        preCounter: props.counter
      };
    }
    return null;
  }

  componentDidMount() {
    // 在组件挂载后,将计数数字变为1
    this.setState({
      counter: 1
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world! {this.state.counter}</h1>
      </div>
    );
  }
}

export default SampleComponent;

2、更新阶段

更新阶段(Updating)涉及到组件在接收到新的props或者状态发生变化时的一系列生命周期方法

(1)static getDerivedStateFromProps(props, state)
  • 当组件接收到新的props时调用,在这个方法中可以根据新的props来更新组件的state。
  • 通过返回一个对象来更新当前的state,如果不需要更新则返回null。
(2)shouldComponentUpdate(nextProps, nextState)
  • 用于决定组件是否需要重新渲染,可以通过返回false来阻止不必要的渲染。
  • 在这个方法中可以比较当前的props和state与下一个props和state的值,来决定是否需要更新。
(3)render()
  • 重新渲染组件的UI结构,根据最新的props和state生成组件的内容。
(4)getSnapshotBeforeUpdate(prevProps, prevState)
  • 在最近一次渲染输出(提交到DOM上)之前调用,可以用于获取当前DOM的快照信息。
  • 返回的值将作为第三个参数传递给componentDidUpdate方法。
(5)componentDidUpdate(prevProps, prevState, snapshot)
  • 组件更新完成后调用,通常用于处理更新后的操作,如数据同步、DOM操作等。
  • 可以在这个方法中执行依赖于更新后DOM状态的操作。

在更新阶段,组件可能会多次重新渲染,这些生命周期方法帮助我们控制组件的更新行为,提高性能并确保组件状态的正确性。通过合理地使用这些方法,我们可以在组件更新时做出相应的处理,保持组件的稳定性和效率。

import React, { Component } from 'react';

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    console.log('Component is being constructed');
  }
 
  static getDerivedStateFromProps(props, state) {
    // 根据props更新state
  }
 
  shouldComponentUpdate(nextProps, nextState) {
    // 控制组件是否重新渲染
    return true;
  }
 
  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Increment
        </button>
      </div>
    );
  }
 
  getSnapshotBeforeUpdate(prevProps, prevState) {
    // 在组件更新前获取某些信息,如滚动位置
  }
 
  componentDidUpdate(prevProps, prevState, snapshotValue) {
    // 组件更新后的操作
  }
 

}

3、卸载阶段

在卸载阶段,只有一个生命周期函数,即componentWillUnmount()。这个方法会在组件即将被卸载和销毁之前被调用。

(1)componentWillUnmount()
  • 清除定时器
  • 取消网络请求
  • 取消在componentDidMount()中创建的订阅等操作
import React, { Component } from 'react';
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    console.log('Component is being constructed');
  }
 
  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Increment
        </button>
      </div>
    );
  }
 
  componentWillUnmount() {
    // 组件卸载前的操作
    console.log('Component is about to unmount');
  }
}

4、错误处理阶段

在React中,有两个生命周期方法用于处理组件内部错误的情况

(1)static getDerivedStateFromError(error)
  • 当子组件抛出错误时调用,用于更新父组件的state以渲染降级UI(Fallback UI)。
  • 这个静态方法允许父组件捕获子组件抛出的错误,并在发生错误时更新状态,以渲染备用UI来展示用户。
(2)componentDidCatch(error, info)
  • 在后代组件抛出错误后被调用,用于捕获组件内部的JavaScript错误、网络请求失败等异常情况,进行错误处理。
  • 接收两个参数:
    • error:表示抛出的错误对象。
    • info:一个对象,其中包含有关组件引发错误的堆栈信息,通常包含componentStack等信息。

5、react常见生命周期

(1)挂载阶段

  • 首先执行constructor构造方法,用于创建组件实例。
  • 接着执行render方法,返回需要渲染的内容。
  • React将渲染内容挂载到DOM树上。
  • 挂载完成后会执行componentDidMount生命周期函数,允许执行一些初始化操作,如数据获取或订阅事件。

(2)更新阶段

  • 当组件接收到新的props或调用setState、forceUpdate时,会重新调用render方法。
  • render方法返回的内容将会重新挂载到DOM树上。
  • 挂载完成后会执行componentDidUpdate生命周期函数,可以执行更新后的操作,如数据同步或DOM操作。

(3)卸载阶段

移除组件时,执行componentWillUnmount生命周期函数,用于清理工作,如清除定时器或取消订阅

6、react主要生命周期

(1)getDefaultProps:在组件创建之前调用一次,用来初始化组件的Props(属性)。

(2)getInitialState:用于初始化组件的state(状态)值。

(3)componentWillMount:在React旧版本中存在,但在React 16中已被废弃。在组件创建后、render之前调用,已不推荐在此阶段执行任何操作。

(4)render:是唯一必须实现的生命周期方法。根据props和state返回一个jsx元素,用于渲染组件到界面上。有时也可返回null或false。

(5)componentDidMount:在组件挂载后立即调用,表示组件挂载完成。适合执行需要DOM操作或发起网络请求的操作,因为此时组件已经插入DOM树中。这也是推荐用于发起网络请求的时机。

参考文章:前端React篇之React的生命周期有哪些?_react 生命周期-CSDN博客

3-5、React Hooks

Hooks简介

Hooks,可以翻译成钩子

在类组件中钩子函数就是生命周期函数,Hooks 主要用在函数组件

类组件的不足:

  • 在一个钩子里可能有很多业务代码。
  • 一个业务很可能出现在多个钩子里。
  • class 组件中的 this 指向问题。

类组件通过在构造器中直接使用 this.state = {} 给组件设置状态值,而函数组件不行。因为函数执行完后会销毁内容,在函数内声明的变量就会自定销毁,所以函数组件无法设置状态,于是 react 提供了 Hooks。

Hooks 的分类

Hook 分为2种,基础 Hook 和额外的 Hook。

基础 Hook:

  • useState
  • useEffect
  • useContext

额外的 Hook:

  • useReducer
  • useCallback
  • useMemo
  • useRef

所有 Hook 都以 use 开头。

Hook 的使用

所有 Hook 都在 react 模块下,使用时需进行引入。

import { useState } from 'react';
1、useState

(1) useState 向组件引入新的状态,这个状态会被保留在 react 中。

(2) useState 只有一个参数,这个参数是初始状态值,它可以是任意类型。

(3) useState 可以多次调用,意味着可以为组件传入多个状态。

(4) useState 返回值是一个数组,第一个元素就是我们定义的 state,第二个元素就是修改这个 state 的方法。接收 useState 的返回值使用数组结构语法,我们可以随意为 state 起名字。修改 state 的方法必须是 set + 状态名首字母大写构成,不按照约定写就会报错。

(5) useState 的参数也可以是一个函数,这个函数的返回值就是我们的初始状态。

function Counter() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <h3>{count}</h3>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

注意:修改状态方法

修改状态的方法 set + 状态变量名:

  • 这个方法的参数可以是值(替换方法),也可以是一个函数。如果是函数,那么这个函数的参数就是初始状态值,这个方法就是更新方法。
  • 这个方法是异步的。
function Counter() {
  const [count, setCount] = useState(() => 1);
  function handleCount(a) {
    setCount(a + 1);
    document.title = a;
  }
  return (
    <div>
      <h3>{count}</h3>
      <button onClick={() => handleCount(count)}>+</button>
    </div>
  );
}

在点击按钮后,计数器显示数字变为了2,但页面的标题还是1

解决办法:

使用useRef存储数据,useEffect监听数据变化,并进行更新。

2、useEffect

主要作用就是将副作用代码添加到函数组件内。所谓的副作用代码就是 dom 更改、计时器、数据请求等。

使用场景:

useEffect(() => {}) 这种写法代表两个生命周期函数 componentDidMount 和 componentDidUpdate。
useEffect(() => {}, []) 这种写法代表生命周期函数 componentDidMount。
useEffect(() => () => {}) 这种写法代表组件卸载之前 componentWillUnmount 和 componentDidUpdate 两个生命周期函数。

(1)useEffect(() => {})
function Counter() {
  const [count, setCount] = useState(() => 1);
  function handleCount(a) {
    setCount(a + 1);
    document.title = a;
  }
  useEffect(() => {
    console.log('hahaha');
  });
  return (
    <div>
      <h3>{count}</h3>
      <button onClick={() => handleCount(count)}>+</button>
    </div>
  );
}

在页面加载完成和每次点击按钮让计数器自增后,控制台都会打印 hahaha。

useEffect Hook 实现了生命周期函数 componentDidMount 和 componentDidUpdate 的功能。

(2)useEffect(() => {}, [])
function Counter() {
  const [count, setCount] = useState(() => 1);
  function handleCount(a) {
    setCount(a + 1);
    document.title = a;
  }
  useEffect(() => {
    console.log('hahaha');
  }, []);
  return (
    <div>
      <h3>{count}</h3>
      <button onClick={() => handleCount(count)}>+</button>
    </div>
  );
}

 useEffect 钩子函数传入了一个空数组作为第2个参数,当页面加载完成时控制台会打印 hahaha,点击按钮更新计数器后不再打印。

实现了生命周期函数 componentDidMount 的功能。

(3)useEffect(() => () => {})
function Counter(props) {
  const [count, setCount] = useState(() => 1);
  function handleCount(a) {
    setCount(a + 1);
    document.title = a;
  }
  useEffect(() => () => {
    console.log('hahaha');
  }, []);
  return (
    <div>
      <h3>{count}</h3>
      <button onClick={() => handleCount(count)}>+</button>
      <button onClick={() => props.root.unmount()}>卸载组件</button>
    </div>
  );
}

点击按钮更新计数器和点击卸载组件时控制台都会打印 hahaha。

实现了生命周期函数 componentWillUnmount 和 componentDidUpdate 的功能。

(4)useEffect 第二个参数的使用

useEffect 钩子函数的第二个参数,正常添加空数组,代表的生命周期是 componentDidMount。即使我们修改了 state,useEffect 也只会调用一次。

如果我们想让某个 state 发生改变的时候,继续调用 useEffect,就需要把这个状态添加到第二个参数的数组中。

function Counter() {
  const [count, setCount] = useState(() => 1);
  const [person, setPerson] = useState({ name: 'zhangsan' });
  function handleCount(a) {
    setCount(a + 1);
    document.title = a;
  }
  useEffect(() => {
    console.log('计数器改变');
  }, [count]); // count 一旦发生改变,就会执行 useEffect
  return (
    <div>
      <h3>{count}</h3>
      <h3>{person.name}</h3>
      <button onClick={() => handleCount(count)}>+</button>
      <button onClick={() => setPerson({ name: 'lisi' })}>更改person</button>
    </div>
  );
}

useEffect 第二个参数传递了 count,那么将会在 count 状态更新时才会执行传入的函数。

(5)useEffect 的异步处理
// 案例
function Counter() {
  function asyncFn() {
    setTimeout(() => {
      console.log('hahaha');
    }, 1000);
  }
  useEffect(() => {
    asyncFn();
  });
  return (
    <div>
    </div>
  );
}

声明了一个异步函数,然后在 useEffect 中调用,预览正常,在页面加载完成1秒后打印 hahaha

在 useEffect 中如果使用了异步函数,那就需要定义一个自调用函数。如:

function Counter() {
  function asyncFn() {
    setTimeout(() => {
      console.log('hahaha');
    }, 1000);
  }
  useEffect(() => {
    (async () => {
      await asyncFn();
    })();
  });
  return (
    <div>
    </div>
  );
}
3、useContext

useContext 用于父组件向子孙组件传递数据,不需要再使用通过 props 从父组件向子组件逐级传递数据。

如果组件多层嵌套,使用 props 来传值显得极其复杂,这时就需要使用 useContext。

前面通信方式已经介绍过了

4、useReducer

useReducer 是 useState 的替代方案,将组件的所有状态更新逻辑整合到一个外部函数中,这个函数叫作 reducer。

因为对于拥有许多状态更新逻辑的组件来说,过于分散的事件处理程序可能会令人头大。

可以通过三个步骤将 useState 迁移到 useReducer:

  • (1) 将设置状态的逻辑修改成 dispatch 的一个 action;
  • (2) 编写 一个 reducer 函数;
  • (3) 在组件中 使用 reducer。
function Counter() {
  const initState = { count: 0 };
  function reducer(state, action) {
    switch (action.type) {
      case 'increment':
        return { count: state.count + 1 };
      case 'decrement':
        return { count: state.count - 1 };
      default:
        return { count: state };
    }
  }
  const [state, dispatch] = useReducer(reducer, initState);
  return (
    <div>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <span>{state.count}</span>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
    </div>
  );
}
5、useMemo

useMemo 是一种性能优化的手段,主要用途就是对状态 state 的记忆,并且还具有缓存功能,类似于 vue 中的计算属性。还有一个作用,避免在代码中参与大量运算。

useMemo 接收2个参数,第1个参数为执行运算的函数,第2个参数为要监控的状态。

function Counter() {
  const [count, setCount] = useState(0);
  const value = useMemo(function () {
    return count * 10;
  }, [count]); // 数组中的元素就是 useMemo 监控的状态
  return (
    <div>
      <h3>{count}</h3>
      <h3>{value}</h3>
      <button onClick={() => setCount(count + 1)}>按钮</button>
    </div>
  );
}
6、useRef

useRef 用于获取组件中的 dom 对象

前面通信方式已经介绍过了

7、memo

memo 是一个高阶组件(参数又是一个组件),功能是对组件进行记忆。

// 案例
function App() {
  const [count, setCount] = useState(0);
  const fn = function () {
    console.log('hahaha');
  };
  return (
    <div>
      {count}
      <button onClick={() => setCount(count + 1)}>增加</button>
      <Heads fn={fn}></Heads>
    </div>
  );
}
 
function Heads(props) {
  console.log('我被渲染了');
  return <button>按钮</button>;
}

我们定义了一个父组件 App 和一个子组件 Heads。

在 count 变化时,子组件重新被渲染。

当前组件内的视图没有发生改变,但被重渲染了,这时就需要借助 memo。

function App() {
  const [count, setCount] = useState(0);
  const fn = function () {
    console.log('hahaha');
  };
  return (
    <div>
      {count}
      <button onClick={() => setCount(count + 1)}>增加</button>
      <Heads fn={fn}></Heads>
    </div>
  );
}
 
const Heads = memo(function (props) {
  console.log('我被渲染了');
  return <button>按钮</button>;
});

memo 的参数是一个组件,返回值为一个高阶组件,可以对传入的组件进行记忆,不修改就不会重新渲染。

但上述案例中,点击按钮时子组件还是会重新渲染。原因在于父组件给子组件传递了一个 fn,当点击按钮时,父组件重新渲染导致 fn 被赋值,fn 修改就会导致子组件重新渲染。

要实现前面的案例传入 fn 不让子组件重新渲染,需要使用 useCallback Hook。

8、useCallback

useCallback 是一个允许我们在多次渲染中缓存函数的 React Hook,它返回一个 memoized 回调函数。

useMemo 是对数据的记忆,useCallback 是对函数的记忆。

useCallback 有2个参数,第1个参数为要缓存的函数,第2个参数是一个数组,表示在哪些响应值(包括 props 、state 和所有在组件内部直接声明的变量和函数)变化时更新函数。

// 案例
function App() {
  const [count, setCount] = useState(1);
  const fn = useCallback(function () {
    return count;
  }, []);
  return (
    <div>
      {count}
      <button onClick={() => setCount(count + 1)}>增加</button>
      <Heads fn={fn}></Heads>
    </div>
  );
}
 
const Heads = memo(function (props) {
  return <button onClick={() => console.log(`我被渲染了${props.fn()}次`)}>按钮</button>;
});

这里我们使用 useCallback 将函数 fn 进行缓存,这时再去点击增加按钮,将不会再重新渲染子组件

function App() {
  const [count, setCount] = useState(1);
  const fn = useCallback(function () {
    return count;
  }, [count]);
  return (
    <div>
      {count}
      <button onClick={() => setCount(count + 1)}>增加</button>
      <Heads fn={fn}></Heads>
    </div>
  );
}
 
const Heads = memo(function (props) {
  return <button onClick={() => console.log(`我被渲染了${props.fn()}次`)}>按钮</button>;
});

useCallback 中加入了第二个参数,数组中有一个元素 count,表示在 count 变化时更新函数 fn。这时在点击增加按钮,就会重新渲染子组件。

9、自定义 Hook

自定义 Hook 就是自己封装的函数功能和 react 中内置的 Hook 进行结合,用于组件间共享逻辑

自定义 Hook 必须以 use 开头

const useGet = function ({ path, params }) {
  const [data, setData] = useState({});
  useEffect(() => {
    axios.get(path, params).then(res => {
      setData(res.data);
    });
  }, []);
  return data;
};



// App.jsx
function App() {
  const data = useGet({ path: 'https://conduit.productionready.io/api/articles', params: {}});
  if (!data.articles) {
    return <div>
      请求中...
    </div>;
  }
  return (
    <div>
      {data.articles.map(item => <p>{item.title}</p>)}
    </div>
  );
}

参考文章:

React04-Hooks 详解_react hooks-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值