你已经准备好了,让我们先大致了解一下 React 吧!
React 是什么?
React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库。使用 React 可以将一些简短、独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作“组件”。
咱们再学习组件之前先了解一下,基础知识JSX
知识点扩展-JSX
jsx 表达式
import '../assets/Apptest.css';
const Sjsx = () =>{
const styleObj = {
color: '#000000'
}
const listonstyle = {
listStyle: 'none'
}
const name = '柴柴'
const Elem = <h2>您好,我叫,{name}</h2> // 只能执行最后一项
return (
{name}
{Elem}
)
}
// 您好我叫,柴柴
jsx 列表渲染
const Sjsx = () =>{
const styleObj = {
color: '#000000'
}
const listonstyle = {
listStyle: 'none'
}
// JSX列表渲染
const songs = [
{ id: 1, name: '我是列表1' },
{ id: 2, name: '我是列表2' },
{ id: 3, name: '我是列表3' }
]
return (
<ul>{songs.map(item =>
<li style={listonstyle} className='title' key={item.id}>
{item.name}
</li>)}
</ul>
)
}
// 我是列表1,我是列表2,我是列表3
jsx 条件渲染
const Sjsx = () =>{
// JSX列表渲染
const flag = true
return (
{flag ? 'react真有趣':'vue真有趣'}
{flag ? <span>this is span</span> : null},
)
}
//react真有趣,this is span
JSX样式处理
const Sjsx = () =>{
return (
<div style={{ color: 'green' }}>
this is a div
</div>
)
}
行内样式 - style - 更优写法 *
const styleObj = {
color: '#000000'
}
const Sjsx = () =>{
return (
<div style={styleObj}>
<span>JSX样式处理行内样式</span>
</div>
)
}
字符串、数值、布尔值、null、undefined、object([] / {} ) 1 + 2、'abc'.split('')、['a', 'b'].join('-'),fn()if 语句/ switch-case 语句/ 变量声明语句,这些叫做语句,不是表达式,不能出现在 {} 中!!
类组件
state
// 类组件模板
import React,{Component} from 'react'
class StateClass extends Component {
state = {
} // 初始值
this.setState((props,state)=>{
})
onchangeHandle = () =>{
/***
*/
this.setstate((props,state)=>{
})
}
}
-----state的使用------
class Childs extends Component {
state = {
num: '我是子组件的值'
// Boolean Array Object,String,Number
}
onHandleChange = () => {
this.setState((v, p)=>{
return {
// Boolean Array Object,String,Number
num: p.num, // 获取到最新的值
// name:props.name,
// sex:props.sex
}
})
}
render() {
return (
<div>
{this.state.num}
<button onClick={this.onHandleChange}>变化</button>
</div>
)
}
}
class App extends Component {
render() {
return (
<Childs num="他说他是个男孩" />
)
}
}
export default App
// 我是子组件的值 点击按钮后 变成 他说他是个男孩
props props:属性,是一个 JavaScript 对象。
- props 是调用方法传递给组件的数据(类似于函数的形参), 而state是在组件内被组件自己管理的数据(类似于在一个函数内声明的变量)
-props不可修改的,所有
React组件都必须像纯函数一样保护它们的props不被更改,由于props是传入的,并且它们不能更改,
// 类组件模板
-----props的使用------
class Childs extends Component {
state = {
num: '我是子组件的值'
// Boolean Array Object,String,Number
}
render() {
const { name, age, sex } = this.props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
);
}
}
class App extends Component {
render() {
return (
// Boolean Array Object,String,Number
<Childs name="jerry" age="19" sex="男" />
)
}
}
export default App
// 姓名:jerry,性别:男,age:19
补充知识点
let arr1 = [1, 3, 5, 7, 9];
let arr2 = [2, 4, 6, 8, 10];
console.log('展开数组', ...arr1); // 展开数组
let arr3 = [arr1, arr2]; // 数组合并
console.log('数组合并', arr3);
let copyArr = [...arr1]; // 数组复制
console.log('数组复制', copyArr);
// 函数实参
function log(x, y, z) {
console.log('函数实参', x, y, z)
}
let args = [1, 1, 1, 1, 1];
log(...args);
// 函数行参
function sum(...numbers) {
return numbers.reduce((pre, cur) => {
return pre + cur;
})
}
console.log('函数行参', sum(1, 2, 3, 4));
let obj1 = {a: 1, b: 2};
let obj2 = {c: 3, d: 4};
let obj3 = {...obj1, ...obj2}; // 对象合并
console.log('对象合并', obj3);
let copyObj = {...obj1}; // 对象复制
console.log('对象复制', copyObj);
let person = {name: 'tom', aget: '18'};
console.log('对象展开', ...person); // 对象展开失败
补充:props和state区别:
- props和state都是用来存储数据的
-
props存储的是父组件传递归来的数据
-
state存储的是自己的数据
-
props只读的
-
state可读可写
-
生命周期旧(旧)
在 React 中,对于每一次由状态改变导致页面视图的改变,都会经历两个阶段:render 阶段、commit 阶段。
只有 class 组件才有生命周期,因为 class 组件会创建对应的实例,而函数组件不会。组件实例从被创建到被销毁的过程称为组件的生命周期。
三个阶段
-
初始化(挂载)阶段: 由 ReactDOM.render() 触发——初次渲染
-
constuctor()
-
componentWillMount()
-
render()=======> 必须使用的一个
-
componentDidMount() =====> 常用
一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
-
-
更新阶段: 由组件内部 this.setSate() 或 父组件render 触发
-
shouldComponentUpdate()
-
componentWillUpdate()
-
render()=======> 必须使用的一个
-
cmponentDidUpdate() 18更新后:
-
-
卸载组件由:root.unmount()* 触发
- componentWillUnmount() =====> 常用
一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
componentReceiveProps:如果父组件导致组件重新渲染,即使props没有更改,也会调用此方法。如果只想处理更改,请确保进行当前值与变更值的比较。
因此,componentReceiveProps并不是由props的变化触发的,而是由父组件的更新触发的
操作实例: 利用求和梳理一遍整个生命周期的执行顺序
import React,{Component} from 'react'
import ReactDOM from 'react-dom'
// 创建子组件
class Count extends Component {
// 构造器----忽略
state = {
// 创建初始化参数
count: 0,
}
addHandle = () => {
//增加的逻辑处理
// 获取原状态
const { count } = this.state
// 更新状态
this.setState((props, state) => {
return {
count: count + 1
}
})
}
deathHandle = () => {
// 卸载的逻辑处理
root.unmount(document.getElementById('root'))
}
force = () => {
// 强制更新组件
this.forceUpdate()
}
/***
组件将要挂载的钩子
componentWillMount ====> 改用UNSAFE_componentWillMount
**/
componentWillMount() {
console.log("count =====>componentWillMount")
}
/***
组件挂载完毕的钩子
**/
componentDidMount() {
console.log("count =====>componentDidMount")
}
/***
组件将要卸载的钩子
**/
componentWillUnmount() {
console.log("count =====>componentWillUnmount")
}
/**
控制组件更新的“阀门”
**/
shouldComponentUpdate() {
console.log('Count =====> shouldComponentUpdate');
return true
}
/**
组件将要更新的钩子
**/
componentWillUpdate() {
console.log("Count ====>componentWillUpdate")
}
/***
组件更新完毕的钩子
**/
componentDidUpdate() {
console.log('Count--- componentDidUpdate');
}
render() {
console.log('Count---render');
// 渲染dom
const { count } = this.state
return (
<div>
<h2>当前求和为:{count}</h2>
<button onClick={this.addHandle}>新增</button>
<button onClick={this.deathHandle}>卸载组件</button>
<button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
</div>
)
}
}
/****
* 执行顺序:
* ---- 初始化阶段------
* count =====>componentWillMount
* Count---render
* count---componentDidMount
* count =====>componentWillUnmount
*
*
* ---- 更新阶段-----
* Count =====> shouldComponentUpdate
* Count ====>componentWillUpdate
* Count---render
* Count--- componentDidUpdate
*
* --- 卸载组件-----
* count =====>componentWillUnmount
*
*
*
*/
// 挂载
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Count />,
<App />
</React.StrictMode>
);
出现新的生命周期缘由
旧的生命周期十分完整,基本可以捕捉到组件更新的每一个state/props/ref,没有什从逻辑上的毛病。但是官方react打算在17版本推出新的Async Rendering,提出一种可被打断的生命周期,而可以被打断的阶段正是实际dom挂载之前的虚拟dom构建阶段,也就是要被去掉的三个生命周期
生命周期一旦被打断,下次恢复的时候又会再跑一次之前的生命周期,
- 标记为不安全: componentWillMount(), componentWillReceiveProps(), componentWillUpdate
现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用
- 新的: static getDerivedStateFromProps, getSnapshotBeforeUpdate
生命周期的三个阶段(新)
三个阶段:
1.初始化(挂载)阶段: 由 ReactDOM.render() 触发—初次渲染
-
constructor()
-
getDerivedStateFromProps
-
render() =====> 必须使用的一个
-
componentDidMount() =====> 常用
一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
2.更新阶段: 由组件内部**this.setSate()**或父组件重新 render 触发
-
getDerivedStateFromProps,
-
shouldComponentUpdate(),
-
render() =====> 必须使用的一个,
-
getSnapshotBeforeUpdate,
-
componentDidUpdate(),
18更新后:
-
卸载组件: 由 root.unmount() 触发
-
componentWillUnmount() =====> 常用
一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
import React, { StrictMode, Component } from "react";
import { createRoot } from "react-dom/client";
\
import App from "./App";
//创建组件
class Count extends Component {
//构造器
//初始化状态
state = { count: 0 };
//加1按钮的回调
add = () => {
//获取原状态
const { count } = this.state;
//更新状态
this.setState({ count: count + 1 });
};
//卸载组件按钮的回调
death = () => {
root.unmount(document.getElementById("test"));
};
//强制更新按钮的回调
force = () => {
this.forceUpdate();
};
//若state的值在任何时候都取决于props,那么可以使用getDerivedStateFromProps
static getDerivedStateFromProps(props, state) {
console.log("getDerivedStateFromProps", props, state);
return null;
}
//在更新之前获取快照
getSnapshotBeforeUpdate() {
console.log("getSnapshotBeforeUpdate");
return "atguigu";
}
//组件挂载完毕的钩子
componentDidMount() {
console.log("Count---componentDidMount");
}
//组件将要卸载的钩子
componentWillUnmount() {
console.log("Count---componentWillUnmount");
}
//控制组件更新的“阀门”
shouldComponentUpdate() {
console.log("Count---shouldComponentUpdate");
return true;
}
//组件更新完毕的钩子
componentDidUpdate(preProps, preState, snapshotValue) {
console.log(
"Count---componentDidUpdate",
preProps,
preState,
snapshotValue
);
}
/****
* 执行顺序:
* ---- 初始化阶段------
* count =====>getDerivedStateFromProps {count: 199} {count: 0}
* Count---render
* count---componentDidMount
* count =====>componentWillUnmount
* count---componentDidMount
*
*
* ---- 更新阶段-----
* ---- 强制更新组件---
* Count =====> getDerivedStateFromProps
* Count ====>shouldComponentUpdate
* Count---render
* Count --- getSnapshotBeforeUpdate
* Count--- componentDidUpdate
*
* --- 卸载组件-----
* count =====>componentWillUnmount
*
*
*
*/
render() {
console.log("Count---render");
const { count } = this.state;
return (
<div>
<h2>当前求和为:{count}</h2>
<button onClick={this.add}>点我+1</button>
<button onClick={this.death}>卸载组件</button>
<button onClick={this.force}>
不更改任何状态中的数据,强制更新一下
</button>
</div>
);
}
}
\
//渲染组件
\
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
\
root.render(
<StrictMode>
<Count count={199} />
<App />
</StrictMode>
);
具体方法详解
1、constructor(props): 如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。
在 React 组件挂载之前,会调用它的构造函数。在为 React.Component 子类实现构造函数时,应在其他语句之前 调用 super(props)。 否则,this.props 在构造函数中可能会出现未定义的 bug。
通常,在 React 中,构造函数仅用于以下两种情况:
通过给 this.state 赋值对象来初始化内部 state 为事件处理函数绑定实例 在 constructor() 函数中不要调用 setState() 方法。如果组件需要使用内部 state,请直接在构造函数中为 this.state 赋值初始化state:
2、render(): 是 class 组件中唯一必须实现的方法
当 render 被调用时,它会检查 this.props 和 this.state 的变化并返回以下类型之一:
fragments 的知识点的补充
class App extends React.Component {
state = {
items: [
{
id: '`2`',
name: '计算机',
description: '用来计算的仪器...'
},
{
id: '2',
name: '显示器',
description: '以视觉方式显示信息的装置...'
}
]
}
render() {
return <Glossary items={this.state.items} ></Glossary>
}
}
function Glossary(props) {
return (
<dl>
{props.items.map(item => (
// 没有 `key`,React 会发出一个关键警告
<React.Fragment key={item.id}>
<dt>{item.name}</dt>
<dd>{item.description}</dd>
</React.Fragment>
))}
</dl>
)
}
React 元素。通常通过 JSX 创建。例如,<div/> 会被 React 渲染为 DOM 节点, 会被 React 渲染为自定义组件,无论是<div /> 还是 均为 React 元素。
数组或fragments。 使得 render 方法可以返回多个元素。欲了解更多详细信息,请参阅fragments 文档。
Portals。可以渲染子节点到不同的 DOM 子树中。欲了解更多详细信息,请参阅有关 portals 的文档
字符串或数值类型。它们在DOM 中会被渲染为文本节点
布尔类型或 null。什么都不渲染。(主要用于支持返回 test
&& <Child /> 的模式,其中test 为布尔类型)
注意: render() 函数应该为纯函数,不进行实际上的渲染动作,它只是一个JSX描述的结构,最终是由React来进行渲染过程。
render函数不应该有任何操作,对页面的描述完全取决于this.props 和 this.state返回的结果。这意味着在不修改组件 state 的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互。可以理解为: UI = render(data)
如需与浏览器进行交互,请在componentDidMount() 或 其他生命周期方法 中执行你的操作。保持 render() 为纯函数,可以使组件更容易使用、维护。
3、componentDidMount(): 会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。
如需通过网络请求获取数据,此处是实例化请求的好地方。
这个方法也是比较适合添加订阅的地方。如果添加了订阅,请不要忘记在 componentWillUnmount() 里取消订阅
可以在 componentDidMount() 里直接调用 setState()。它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前。如此保证了即使在 render() 两次调用的情况下,用户也不会看到中间状态。
注意: 请谨慎使用该模式,因为它会导致性能问题。通常,应该在 constructor() 中初始化 state。如果你的渲染依赖于 DOM 节点的大小或位置,比如实现 modals 和 tooltips 等情况下,可以使用此方式处理
4、componentDidUpdate(prevProps, prevState, snapshot): 会在更新后会被立即调用。首次渲染不会执行此方法。当组件更新后,可以在此处对 DOM 进行操作。
如果对更新前后的 props 进行了比较,也可以选择在此处进行网络请求。
也可以在 componentDidUpdate() 中直接调用 setState(),但请注意它必须被包裹在一个条件语句里,否则会导致死循环。它还会导致额外的重新渲染,虽然用户不可见,但会影响组件性能。
5、componentWillUnmount(): 会在组件卸载及销毁之前直接调用。
在此方法中执行必要的 清理操作,例如,清除timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。
componentWillUnmount() 中不应调用 setState(),因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。
this 问题说明
你必须谨慎对待 JSX 回调函数中的this, 在javascript中,class 的方法 默认不会绑定this, 如果你忘记绑定 this.handleClick 并把它传入了onClick,当你调用了这个函数的时候 this的值为undefined。
这并不是React特有的行为;这其实与JavaScript函数工作原理有关。通常情况下,如果你没有在方法后面添加() ,例如onClick={this.handleClick} 你应该为这个方法绑定 this 这里我们作为了解内容,随着js标准的发展,主流的写法已经变成了class fields,无需考虑太多this问题
函数组件
能够独立使用函数完成react组件的创建和渲染
react 组件得本质
Modal => View 得映射,其中 Modal 对应 react 中的 state + props。
react 通过 JSX 将 Modal 与 View 进行 数据绑定。
概念
使用 JS 的函数(或箭头函数)创建的组件,就叫做
函数组件
- 组件的定义
- 组件的state状态定义,组件的状态获取,组件的绑定
- 组件的setstate状态的修改
- 组件的使用props
声明: 对于函数组件(Function Component)来说,它没有 class 组件中的 componentDidMount、componentDidUpdate 等生命周期方法,也没有 State,但这些可以通过 React Hook 实现。
现在我们引入一个新的概念:Hooks,顾名思义什么是Hooks, 英文理解: 钩子 就是我们需要什么钩子,通过Hooks 把需要的钩出来实现显示
Hooks 约定和规则
在深入研究各种钩子之前,看看适用于它们的约定和规则可能会有所帮助。以下是适用于钩子的一些规则。
- 挂钩的命名约定应以前缀开头
use。所以,我们可以有useState,useEffect等等。如果你使用像 Atom 和 VSCode 这样的现代代码编辑器,ESLint 插件对于 React 钩子来说可能是一个非常有用的功能。该插件提供了有关最佳实践的有用警告和提示。 - 钩子必须在组件的顶层调用,在 return 语句之前。它们不能在条件语句、循环或嵌套函数中调用。
- 必须从 React 函数(在 React 组件或另一个钩子内部)调用钩子。不应从 Vanilla JS 函数调用它。 我会在下面标注清楚那些需要在项目中使用,哪些只用来了解
- useState()
- useEffect()
- useContext()
- useReducer()
- useRef()
- useCallback()
- useMemo()
- useImperativeHandle()
- useLayoutEffect()
- useDebugValue()
- useDeferredValue()
- useTransition()
- useId()
- useSyncExternalStore()
- useInsertionEffect()
useState钩子
useState 钩子是最基本和最有用的React 钩子, 就像其他内置的钩子一样,这个钩子必须被导入React 才能在我们的应用程序中使用
import {useState} from 'react'
要初始化状态,我们必须声明状态及其更新函数并传递一个初始值。 const [state, updaterFn] = useState()
const [number,setNumber] = useState(0) // Number
const [state, updaterFn] = useState('hello,hooks') // String
const [nos, setnos] = useState(true) // Boolean
const [data, settdata] = useState({name: 'kkb', age: 10}) // object
const [Arraydata, setArraydata] = useState(['FromData', 'slcasss']) // Array
const [objArray, setobjArray] = useState([{id: 1, name: 'vue'},
{id: 2, name: 'react'},
{id: 3, name: 'angalur'}]) // ArrayObject
useState => Number的实例
import { useState } from "react";
import { Button } from "antd";
const App = (props) => {
const [number, setNumber] = useState(0);
const inHandleButton = () =>{
setNumber(number + 1)
}
return (
<div>
{/* 直接拿到 初始化的值 number :0*/}
<h1>{number}</h1>
{/* 通过事件改变 的值 */}
<Button type="primary" onClick={inHandleButton}>Change</Button>
</div>
);
};
export default App;
useState => String实例
import { useState } from "react";
import { Button } from "antd";
const App = (props) => {
const [num, setNum] = useState("在忙吗?");
const inHandleButton = () =>{
setNum(number + '买菜')
setNum('在忙')
}
return (
<div>
{/* 直接拿到 初始化的值 num :0*/}
<h1>{num}</h1>
{/* 通过事件改变 的值 */}
<Button type="primary" onClick={inHandleButton}>Change</Button>
</div>
);
};
export default App;
useState => Boolean 实例
/***
* useEffect
* @type {string}
* useEffect 其实就是让函数组件拥有了生命周期。
* 传给 useEffect 的函数会在浏览器完成布局与绘制之后,在一个延迟事件中被调用。
* 这使得它适用于许多常见的副作用场景,比如
* 设置订阅和事件处理
* 等情况,
* 因为绝大多数操作不应阻塞浏览器对屏幕的更新。
* 结合 useEffect 来实现 useState() // Boolean 场景的使用
*/
/**
* const [data,setData] = useState()
useEffect(()=>{
const fetchData = async() => {
const res = await fetchNewData()
setData(res.data)
}
fetchData()
},[data])
知识扩展:
每一个useEffect中的props和state都是独立的,不是state在不变的effect中变化,
而是每一个effect中有不变的state
*/