类组件和函数组件
1、函数组件
函数组件也称无状态组件,顾名思义就是以函数形态存在的 React 组件。
在 hooks 出现之前,react 中的函数组件通常只考虑负责UI的渲染也就是数据展示,没有自身的状态,没有业务逻辑代码,是一个纯函数。下面这个函数组件就是一个纯函数,它的输出只由参数props决定,不受其他任何因素影响。
function DemoFunction(props) {
const { text } = props
return (
<div className="demoFunction">
<p>{`function 组件所接收到的来自外界的文本内容是:[${text}]`}</p>
</div>
);
}
但是这种函数组件一旦我们需要给组件加状态,那就只能将组件重写为类组件,因为函数组件没有实例,没有生命周期。所以我们说在 hook 之前的函数组件和类组件最大的区别就是状态的有无。
2、类组件
在 React 中,类组件就是基于 ES6 语法,通过继承 React.component 得到的组件。
类组件是有自己的生命周期和数据的 ,负责更新UI。
class Demo extends React.Component {
// 初始化类组件的 state
state = {
text: "111"
};
// 编写生命周期方法 didMount
componentDidMount() {
// 省略业务逻辑
}
// 编写自定义的实例方法
changeText = (newText) => {
// 更新 state
this.setState({
text: newText
});
};
// 编写生命周期方法 render
render() {
return (
<div className="demoClass">
<p>{this.state.text}</p>
<button onClick={() => this.changeText(222)}>点我修改</button>
</div>
);
}
}
3、类组件和函数组件区别
1、表面差异:
- 类组件有生命周期,函数组件没有
- 类组件需要继承 Class,函数组件不需要
- 类组件可以获取实例化的 this,并且基于 this 做各种操作,函数组件不行
- 类组件内部可以定义并维护 state, 函数组件为无状态组件(可以通过hooks实现)
函数组件相比较类组件,优点是更轻量与灵活,便于逻辑的拆分复用。
2、函数式组件捕获了渲染时所使用的值,这是两类组件最大的不同。
具体可查看此篇博客,同时本文部分内容参考【React】函数组件 和 类组件 的区别_react函数组件和类组件的区别-优快云博客
Hook简介
1、hook的由来和作用
react16.8以后的新特性Hooks函数组件在react16.8以前函数组件只能被动接收外部数据,并且没有自己的生命周期钩子函数,函数内部也没有this可用新特性Hookhook推出的动机主要是因为类组件有一下几个不足
- 组件之间复用公共逻辑比较麻烦,以前常见的提取组件公共逻辑的方式有高阶组件/renderProps等,但这些方式或多或少都对原有组件的代码组织方式有一定的破坏性
- 复杂组件变得难以理解(例如相同的逻辑可能要写在不同的生命周期钩子函数里面)
- 难以理解的class(比如对新手来见,class组件中的this不太好理解)
- 新特性hook出现之后,函数组件就可以完全替代类组件,但是这并不是说react官方会抛弃类组件,react官方文档中也表明了没有计划会在react中移除class组件。
注意:hook特性只能在函数组件中使用
2、常见hook及作用
useRef
1、作用
在函数组件中获取DOM元素或者组件对象
2、使用步骤
- 导入useRef函数
import { useRef } from 'react'
- 执行
useRef
函数,返回一个r ef 对象
function 组件名() {
const 变量名 = useRef();
}
- 绑定r e f
找到函数组件中对应的节点,通过 ref
属性将 ref 对象绑定到元素节点身上:
function 组件名() {
const 变量名 = useRef();
return (
<节点 ref={变量名}></节点>
)
}
- 获取 元素节点
当元素通过 ref
绑定了元素后,就可以通过 ref 对象来获取元素节点了:
function 组件名() {
const 变量名 = useRef();
return (
<节点 ref={变量名} onClick={() => {
console.log(变量名.current); // 元素节点
}}></节点>
)
}
3、代码实现
import React,{useRef} from 'react'
import './Ball.css'
export default function Ball() {
const ballRf=useRef()
const changeBall=()=>{
ballRf.current.style.backgroundColor="springgreen"
}
return (
<div>
<div className="box" ref={ballRf}></div>
<button onClick={changeBall}>变化</button>
</div>
)
}
useContext
在Hooks出来之前,开发者都是使用的class组件,通过props传值。现在使用方法组件(Function)开发了,没有constructor构造函数也就没有了props的接收,所以父子组件的传值就成了一个问题。React Hooks
就为我们准备了useContext来解决这个问题。
1、作用
1.1、useContext可以帮助我们跨越组件层级直接传递变量,实现数据共享。
- 这里要注意的是,很多同学觉得可以使用useContext结合useReducer来替代redux,其实两者的作用是不同的。
- useContext:解决组件间传值的问题。
- redux:统一管理应用状态。
- 所以,我们可以使用useContext结合useReducer来模拟一个小型redux场景,而无法替代redux
1.2、Context的作用就是对它所包含的组件树提供全局共享数据的一种技术。
2、代码实例
2.1、新建example.tsx
import React, { useState, createContext, useContext } from "react";
const Example = () => {
const [count, setCount] = useState<number>(0);
return (
<div>
<p>父组件点击数量:{count}</p>
<button onClick={() => setCount(count + 1)}>{"点击+1"}</button>
</div>
);
};
export default Example;
2.2、在example.tsx创建context
const CountContext = createContext(0);
2.3、在example.tsx创建子组件Counter,通过useContext把刚刚创建好的CountContext作为参数传进去,并读取count值
const Counter = () => {
const count = useContext(CountContext);
return <p>子组件获得的点击数量:{count}</p>;
};
2.4、使用CountContext.Provider包裹需要接收参数的子组件,并通过value传值
<CountContext.Provider value={count}>
<Counter />
</CountContext.Provider>
2.5、最终示例代码
import React, { useState, createContext, useContext } from "react";
const CountContext = createContext(0);
const Example = () => {
const [count, setCount] = useState<number>(0);
return (
<div>
<p>父组件点击数量:{count}</p>
<button onClick={() => setCount(count + 1)}>{"点击+1"}</button>
<CountContext.Provider value={count}>
<Counter />
</CountContext.Provider>
</div>
);
};
const Counter = () => {
const count = useContext(CountContext);
return <p>子组件获得的点击数量:{count}</p>;
};
export default Example;
useState
1、作用
useState 为函数组件提供了状态(state)
2、基本使用
2.1、使用步骤
- 导入useState函数
import { useState } from 'react'
- 调用useState函数,并传入状态的初始值,从useState函数的返回值中,拿到状态和修改状态的方法
1.参数是值
const [变量名, 方法名] = useState(数据初始值);
2.参数是函数
const [变量名, 方法名]=useState(()=>{return '计算之后的初始值'})
语法规则:
回调函数return出去的值作为变量名的初始值
回调函数中的逻辑只会在组件初始化的时候执行一次
语法选择:
如果初始化一个普通的数据,直接使用useState(初始值)即可
如果要初始化的数据无法直接得到需要通过计算才能获取到,使用useState(()=>{}
- 在jsx中展示状态
- 使用修改状态的方法更新状态
useEffect
1、作用
- 副作用:副作用是相对与主作用而言的,一个函数除了主作用,其他作用都是副作用,对于React组件来说,主作用是根据数据(state/props)渲染UI,除此之外都是副作用
- 常见的副作用
- 数据请求Ajax发送
- 手动修改DOM
- localStorage操作
- Effect Hook 可以让你在函数组件中执行副作用操作
2、useEffect 的参数
import { useEffect } from 'react'
useEffect(() => {}, []);
useEffect 的参数,主要分为两个,第一个固定是一个回调函数,第二个是一个数组(可选)。
参数传递的不同,useEffect 实现的效果也不同。
2.1、没有第2个参数
当useEffect只有第一个参数时,其作用就是在模拟 componentDidMount
和 componentDidUpdate
生命周期函数。也就是说,useEffect 的第一个回调函数,会在组件首次挂载完成执行一次,同时,后续组件每次更新完成时也会执行。
import {useState,useEffect} from 'react'
export default function Params() {
const [count,setCount]=useState(0)
useEffect(()=>{
console.log('没有第2个参数');
})
return (
<div>
<h1>{count}</h1>
<button onClick={()=>setCount(count+1)}>+1</button>
</div>
)
}
执行的效果是:组件首次挂载完成执行一次,每次单击按钮会执行一次
2.2、第2个参数是空数组
当 useEffect 的第二个参数是一个空数组是,其作用就是在模拟 componentDidMount
生命周期函数。也就是说,useEffect 的第一个回调函数,会在组件首次挂载完成执行一次。
import {useState,useEffect} from 'react'
export default function Params() {
const [count,setCount]=useState(0)
useEffect(()=>{
console.log('没有第2个参数');
},[])
return (
<div>
<h1>{count}</h1>
<button onClick={()=>setCount(count+1)}>+1</button>
</div>
)
}
执行的效果是:只有在组件首次挂载完成执行一次,后续单击不会执行
2.3、第2个参数是非空数组
当 useEffect 的第二个参数是一个非空数组时,其作用就是在模拟 componentDidMount 生命周期函数,同时还可以模拟 Vue 中 watch 的作用。
也就是说,useEffect 的第一个回调函数,会在组件首次挂载完成执行一次。后续,只要当第二个参数的数组中,任意一条数据发生改变,useEffect 的第一个回调函数又会再次执行。
import {useState,useEffect} from 'react'
export default function Params() {
const [count,setCount]=useState(0)
const [name,setName]=useState('张三')
useEffect(()=>{
console.log('第2个参数是非空数字');
},[count])
return (
<div>
<h1>{count}</h1>
<h1>{name}</h1>
<button onClick={()=>setCount(count+1)}>+1</button>
<button onClick={()=>setName('李四')}>更新</button>
</div>
)
}
执行的结果是:在组件首次挂载完成执行一次,后续只有在点击+1操作按钮的时候才会调用useEffect的回调函数。单击更新按钮不会触发useEffect回调函数的执行
2.4、清理副作用
当 useEffect 的第一个参数中,返回了一个函数。那么,返回的这个函数就是在模拟 componentWillUnmount
生命周期函数。也就是说在组件被销毁时,如果有些副作用操作需要被清理,在这里可以写清理副作用的代码。
import { useEffect } from 'react'
export default function ClearEffect() {
useEffect(() => {
let times=setInterval(() => {
console.log('定时执行操作');
}, 1000);
return ()=>{
clearInterval(times)
}
}, [])
return (
<div>this is test</div>
)
}
***************************************************************************************
import ClearEffect from "./ClearEffect"
import { useState } from "react"
export default function App() {
const [flag,setFlag]=useState(true)
return (
<div>
{flag? <ClearEffect/>:null}
<button onClick={()=>setFlag(!flag)}>switch</button>
</div>
)
}
3、useEffect-网络请求
import axios from 'axios'
import React, { useState, useEffect } from 'react'
export default function User() {
const [users, setUsers] = useState([])
const getUsersApi = async () => {
let result = await axios.get('http://39.106.34.185:8888/user')
setUsers(result.data.data.list)
}
useEffect(() => {
getUsersApi()
}, [])
return (
<div>
<table>
<tr>
<td>昵称</td>
<td>手机号</td>
<td>绑定汽车数量</td>
<td>订单数量</td>
<td>未缴费订单数量</td>
<td>状态</td>
<td>创建事件</td>
<td>操作</td>
</tr>
{
users.map(item => <tr key={item.id}>
<td>{item.name}</td>
<td>{item.tel}</td>
<td>{item.carNum}</td>
<td>{item.orderNum}</td>
<td>{item.orderNum}</td>
<td>{item.state==1?'启用':'禁用'}</td>
<td>{item.createTime}</td>
<td>
<span>查看</span>
<span>订单详情</span>
<span>更多</span>
</td>
</tr>)
}
</table>
</div>
)
}
useMemo
1、作用
useMemo的作用类似vue中的计算属性computed,即根据母体数据衍生出新的数据,一旦母体数据发生变化,则useMemo会自动执行
2、基础语法
import { useMemo } from 'react'
const 变量名 = useMemo(() => {
return 计算得到的数据;
}, [依赖的原数据])
最终,计算得到的数据,就会通过 return 保存到变量身上。例如购物车功能
import axios from 'axios'
import React, { useState, useEffect,useMemo } from 'react'
import './ShopCartList.css'
export default function ShopCartList() {
const [list, setList] = useState([])
const fetchShopCartData = async () => {
const result = await
axios.get('http://www.zhaijizhe.cn:3001/shopcart/getShopcartData')
console.log(result.data);
setList(result.data.data)
}
const total=useMemo(()=>{
return list.filter(item=>item.checked).reduce((pre,cur)=>pre+cur.price*cur.num,0)
},[list])
const changeNum=async(_id,n)=>{
let result=await
axios.post('http://www.zhaijizhe.cn:3001/shopcart/changeNum',{_id,n})
if(result.data.code){
fetchShopCartData()
}
}
const checkItem=async(_id)=>{
let result=await
axios.get('http://www.zhaijizhe.cn:3001/shopcart/checkProducts',{params:{_id}})
if(result.data.code){
fetchShopCartData()
}
}
const changeNum1=(_id,index,n)=>{
list[index].num=n
setList(()=>{
return[
...list
]
})
}
const submitChangeNum=async(_id,n)=>{
let result=await
axios.post('http://www.zhaijizhe.cn:3001/shopcart/changeNumByInput',{_id,n})
if(result.data.code){
fetchShopCartData()
}
}
useEffect(() => {
fetchShopCartData()
}, [])
return (
<div>
<h1>购物车</h1>
<table>
<thead>
<tr>
<th>序号</th>
<th>名称</th>
<th>价格</th>
<th>数量</th>
<th>小计</th>
</tr>
</thead>
<tbody>
{
list.map((item,index)=><tr key={item._id}>
<td>
<input type='checkbox'
onChange={()=>checkItem(item._id)}
checked={item.checked}/>
</td>
<td>{item.name}</td>
<td>{item.price}</td>
<td>
<button onClick={()=>changeNum(item._id,-1)}>-</button>
<input type="text" value= {item.num}
onChange={(e)=>
{changeNum1(item._id,index,e.target.value)}}
onBlur={(e)=>
{submitChangeNum(item._id,e.target.value)}}/>
<button onClick={()=>changeNum(item._id,1)}>+</button>
</td>
<td>{item.price*item.num}</td>
</tr>)
}
<tr>
<td colSpan={5} style={{textAlign:'right'}}>
总计:{total}
</td>
</tr>
</tbody>
</table>
</div>
)
}
React.memo
React.memo 可以用来缓存组件。
在 React 中,默认情况下,父组件更新时,无论子组件内部是否改变,子组件也会更新。
但是,从性能考虑,我们希望的是:父组件更新时,如果子组件内部(state 和 props)没有发生改变,子组件不用更新。
解决方法,就是用 React.memo 将子组件缓存起来。
/* 父组件 */
import {useState} from 'react'
import HookChild from './HookChild'
export default function HookParent() {
console.log('父组件');
const [count,setCount]=useState(0)
return (
<div>
<h1>父组件:{count}</h1>
<button onClick={()=>setCount(count+1)}>+1</button>
<HookChild></HookChild>
</div>
)
}
/* 子组件 */
import React from 'react'
function HookChild() {
console.log('子组件');
return (
<h2>子组件</h2>
)
}
export default React.memo(HookChild);
useCallback
useCallback 可以用来缓存函数。
1、基础语法
import { useCallback } from 'react';
const 变量名 = useCallback(缓存的函数, [])
2、应用场景
通常,我们为了减少子组件不必要的更新,会使用 React.memo() 来缓存子组件的状态。
但是,如果父组件传递了函数给子组件,那么,每次父组件更新时,函数都会重新创建。子组件中的 React.memo() 就会判定为父组件传递的内容发生了改变,那么 React.memo() 就不会缓存当前子组件的状态。
因此,为了解决函数的问题,我们需要保存函数的状态,让父组件更新时函数能够缓存下来,因此我们需要使用 useCallback 将传递的子组件的函数状态缓存起来。
/* 父组件 */
import { useCallback, useState } from "react";
import HookChild from "./HookChild";
export default function HookFather() {
console.log('父组件');
const [count, setCount] = useState(0);
const sayHello = () => {
console.log('hello');
}
// 缓存函数状态
const cbSayHello = useCallback(sayHello, []);
return (
<>
<h1>父组件:{count}</h1>
<button onClick={() => setCount(count + 1)}>+1</button>
<HookChild sayHello={cbSayHello}></HookChild>
</>
)
}
/* 子组件 */
import React from 'react'
function HookChild({sayHello}) {
console.log('子组件');
return (
<div>
<h2>子组件</h2>
</div>
)
}
export default React.memo(HookChild)
useCallback和useMemo的区别
useMemo 和 useCallback 是 React 中的自定义 Hook,它们用于优化组件性能。两者的区别主要体现在它们缓存的内容和场景上:
- useMemo 用于缓存计算结果,确保只有在依赖项发生变化时才会重新计算。它接受一个函数和一个依赖项数组作为参数,当依赖项发生变化时,它会重新计算结果并返回缓存的值。这适用于那些计算成本较高的函数,通过避免不必要的重渲染来提高性能。
- useCallback 用于缓存函数,确保只有在依赖项发生变化时才会重新创建函数。它同样接受一个函数作为参数,但返回的是一个记忆化后的函数,这样当依赖项没有变化时,函数不会被重新创建,从而避免在每次渲染时都创建新的函数实例。这适用于那些需要传递给子组件或副作用函数(如 useEffect)的函数,通过避免不必要的子组件重渲染或副作用函数的重复执行来提高性能。
总结来说,useMemo 用于缓存计算结果,而 useCallback 用于缓存函数。两者都旨在减少不必要的重渲染和函数重新创建,从而提高 React 组件的性能。
3、自定义hook
自定义hook提取函数组件公共逻辑
- 自定义hook的作用在于提取公共逻辑,所以一般不会返回一个JSX对象,而是会根据需要返回特定的数据或者方法
- 自定义hook必须要以use开头
假设现在有Test1和Test2两个函数组件,他们有一些公共逻辑需要提取,比如都要从后台获取一个列表数据,然后用户界面的渲染,则现在可以编写一个自定义hook来提取这部分公共逻辑,以避免重复写代码。
import {useState,useEffect} from 'react'
export default function useData(){
const [list,setList] = useState([]);
useEffect(()=>{setTimeout(()=>{
const list = [1,2,3]
setList(list);
},200);},[]);
return {list,}}
//Test1
import useData from '...'
function Test1(){
const {list} = useData();
return
div>
{...用list渲染JSX}
</div>
}
//Test2类似
本文参考react函数组件-优快云博客