组件类的缺点
React 的核心是组件。v16.8 版本之前,组件的标准写法是类(class)。
下面是一个简单的组件类。
import React, { Component } from "react";
export default class Button extends Component {
constructor() {
super();
this.state = { buttonText: "Click me, please" };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(() => {
return { buttonText: "Thanks, been clicked!" };
});
}
render() {
const { buttonText } = this.state;
return ;
}
}
这个组件类仅仅是一个按钮,但可以看到,它的代码已经很"重"了。真实的 React App 由多个类按照层级,一层层构成,复杂度成倍增长。再加入 Redux,就变得更复杂。
Redux 的作者 总结了组件类的几个缺点。
- 大型组件很难拆分和重构,也很难测试。
- 业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。
- 组件类引入了复杂的编程模式
函数组件
React 团队希望,组件不要变成复杂的容器,最好只是数据流的管道。开发者根据需要,组合管道即可。 组件的最佳写法应该是函数,而不是类。
React 早就支持函数组件,下面就是一个例子。
function Welcome(props) {
return Hello, {props.name}
;
}
但是,这种写法有重大限制,必须是纯函数,不能包含状态,也不支持生命周期方法,因此无法取代类。
React Hooks
React Hooks 的设计目的,就是加强版函数组件,完全不使用"类",就能写出一个全功能的组件。
Hook 这个单词的意思是"钩子"。
React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。 React Hooks 就是那些钩子。
React 常用Hook
- useState
- useEffect
- useRef
- useContext
- useReducer
- useCallback
- useMemo
useState
- useState()用于为函数组件引入状态(state)。纯函数不能有状态,所以把状态放在钩子里面。
//引入钩子函数
import {useState} from 'react';
const Test = () => {
// useState的参数为状态初始值
// useSibtate的返回值是一个数组
// 数组第一个成员是变量,第二个成员是函数,用来更新状态,约定是set前缀加上状态的变量名
const [num,setNum] = useState(1)
return (
访问num: {num}
);
}
export default Test
useEffect副作用钩子
useEffect()用来引入具有副作用的操作,最常见的就是向服务器请求数据。
useEffect() 可以实现生命周期函数的效果
import { useState, useEffect } from 'react';
const Test = () => {
const [num, setNum] = useState(1)
const [msg, setMsg] = useState('a')
const [list, setList] = useState([])
// 组件初始化自动执行一次,之后每更新一次执行一次
// 类似 componentDidUpdate
useEffect(()=>{
console.log('没有第二个参数,多次执行');
})
//等价于 componentDidMount , 只在初始化时执行一次
useEffect(()=>{
console.log('第二个参数为空数组,只执行一次');
},[])
// 第二个参数为state, 初次执行后,只有该state改变时执行
useEffect(()=>{
console.log('第二个参数为state数据,数据改变一次执行一次');
},[num])
useEffect(()=>{
//return的回调 等价于 componentWillUnmount() 组件卸载时
return ()=>{
console.log('组件卸载时');
}
},[])
return (
访问num: {num}
访问msg: {msg}
);
}
export default Test
在父组件中实现组件卸载
import Test from './Test'
import { useState } from 'react';
const App = () => {
const [flag,setFlag] = useState(true)
return (
{/* 单击按钮,卸载组件 */}
{ flag ? : null}
);
}
useRef
useRef用来获取DOM元素
import { useRef , useEffect} from "react";
const Test = () => {
//1. 创建引用实例
const btnRef = useRef()
useEffect(()=>{
//3. 通过实例的current属性即可访问DOM对象
btnRef.current.focus()
},[])
return (
{/* 2. 给DOM元素添加ref属性,值为useRef创建的实例 */}
);
}
export default Test
useContext 共享状态钩子
如果需要在组件之间共享状态,可以使用useContext()
- 使用 React Context API,在组件外部建立一个 Context
Context因为需要其它组件共享,所以要单独导出
export const AppContext = createContext()
- 利用父组件状态钩子,创建共享状态
const [city,setCity] = useState('郑州')
- 提供了一个 Context 对象,这个对象可以被子组件共享
value值为共享状态,值为一个对象
Test---{city}
4、在后代组件中引入Context,
import {AppContext} from './Test'
- useContext()钩子函数用来引入 Context 对象,从中获取所需要的状态
import React,{useContext} from 'react';
......
const {city,setCity} = useContext(AppContext)
完整案例
Parent.js
import { createContext, useState } from 'react'
import Child from './Child'
export const AppContext = createContext()
const Parent = () => {
const [city, setCity] = useState('郑州')
return (
Parent---{city}
);
}
export default Parent;
Child.js
import GrandSon from './GrandSon'
const Child = () => {
return (
Child
);
}
export default Child;
GrandSon.js
import React,{useContext} from 'react';
import {AppContext} from './Test'
const GrandSon = () => {
const {city,setCity} = useContext(AppContext)
return (
GrandSon-----{city}
);
}
export default GrandSon;
useReducer
- useReducer 是用于提高应用性能的,当更新逻辑比较复杂时,我们应该考虑使用useReducer
- useReducer 是 useState的代替方案,用于 state 复杂变化
- useReducer 是单个组件状态管理,组件通讯还需要 props
- redux 是全局状态管理,多组件共享数据
useReducer 接受一个 reducer 函数作为参数,
reducer 接受两个参数一个是 state 另一个是 action 。然后返回一个状态 count 和 dispath,count 是返回状态中的值,
而 dispatch 可以发布事件来更新 state 。
import { useReducer } from 'react'
// 定义reducter,不像redux传递type
const reducer = (state,action)=> {
switch(action){
case 'add':
return state + 1;
case 'sub':
return state - 1;
default:
return state;
}
}
const Parent = () => {
// count为变量即状态 , dispatch用来发布事件改变状态
// useReducer第二个参数为count的初始值
const [count, dispatch] = useReducer(reducer,3)
return (
count------{count}
{/* 用dispatch发布事件 */}
);
}
export default Parent;
useMemo
函数组件的每一次调用都会执行其内部的所有逻辑,那么会带来较大的性能损耗。因此useMemo 和useCallback就是解决性能问题的杀手锏。
useCallback和useMemo的参数跟useEffect一致,他们之间最大的区别有是useEffect会用于处理副作用,而前两个hooks不能。
useMemo和useCallback都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,useMemo返回缓存的变量,useCallback返回缓存的函数。
反例: 不相关的val改变,expensive也会执行
import {useState} from 'react';
export default function WithoutMemo() {
const [count, setCount] = useState(1);
const [val, setValue] = useState('');
function expensive() {
console.log('compute');
let sum = 0;
for (let i = 0; i < count * 100; i++) {
sum += i;
}
return sum;
}
return
{count}-{val}-{expensive()}
setValue(event.target.value)}/>
;
}
用useMemo解决
import {useState,useMemo} from 'react';
export default function WithMemo() {
const [count, setCount] = useState(1);
const [val, setValue] = useState('');
// useMemo第一个参数是回调, 第二个是依赖的变量
const expensive = useMemo(() => {
console.log('compute');
let sum = 0;
for (let i = 0; i < count * 100; i++) {
sum += i;
}
return sum;
}, [count]);
return
{count}-{expensive}
{val}
setValue(event.target.value)}/>
;
}
useCallback
useCallback跟useMemo比较类似,但它返回的是缓存的函数。
分析下面的案例:
每次修改count,set.size就会+1,这说明useCallback依赖变量count,count变更时会返回新的函数;而val变更时,set.size不会变,说明返回的是缓存的旧版本函数。
import React, { useState, useCallback } from 'react';
const set = new Set();
export default function Callback() {
const [count, setCount] = useState(1);
const [val, setVal] = useState('');
const callback = useCallback(() => {
console.log(count);
}, [count]);
set.add(callback);
return
{count}
{set.size}
setVal(event.target.value)}/>
;
}
使用场景是:有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。
import React, { useState, useCallback, useEffect } from 'react';
export default function Parent() {
const [count, setCount] = useState(1);
const [val, setVal] = useState('');
//观察子组件userEffect在val值改变时不触发
// const callback = useCallback(() => {
// console.log('cb');
// return count;
// }, [count]);
//观察子组件userEffect在val值改变时触发
const callback = () => {
console.log('cb');
return count;
};
return
{count}
setVal(event.target.value)}/>
;
}
function Child({ callback }) {
console.log('child');
const [count, setCount] = useState(() => callback());
useEffect(() => {
console.log('useeffect');
setCount(callback());
}, [callback]);
return
子------{count}
}
React-router 常用Hook
- useParams
- useHistory
- useLocation
import { useEffect } from 'react'
import { useParams,useLocation,useHistory }