在 React 16.8 之前,函数组件只能是无状态组件,也不能访问 react 生命周期。
hook 做为 react 新增特性,可以让我们在不编写 class 的情况下使用 state 以及其他的 react 特性。
使用函数组件也有以下几点好处:
- 风格清爽,函数式编程风格,函数式组件、状态保存在运行环境、每个功能都包裹在函数中,整体风格更清爽,更优雅。
- 代码量小
- 可以使用函数组合,嵌套,实现功能更加强大的组件。
- 组件不会被实例化,整体渲染性能得到提升。
常用的hooks有以下几种:
- useState,useEffect,useRef,useContext
- useSelector,useDispatch
- 自定义hooks
1.useState(state,setState)
示例:点击按钮,使得按钮的文案由"点击前"变为“点击后”。
传统的写法:
import React, { Component } from "react";
export default class Button extends Component {
constructor() {
super();
this.state = {
buttonText: "点击前"
};
}
handleClick() {
this.setState({
'buttonText': "点击后"
});
}
render() {
const { buttonText } = this.state;
return <button onClick={() => this.handleClick()}>{buttonText}</button>;
}
}
使用hooks的写法:函数组件不需要构造函数,可以通过调用 useState 来初始化 state
import React, { useState } from "react";
export default function Button() {
const [buttonText,setButtonText] = useState("点击前");
const handleClick = () => {
setButtonText("点击后");
}
return <button onClick={handleClick}>{buttonText}</button>;
}
2.useEffect (componentDidMount,componentDidUpdate,componentWillUnmount)
useEffect 拥有两个参数,第一个参数作为回调函数会在浏览器布局和绘制完成后调用,因此它不会阻碍浏览器的渲染进程。
第二个参数是一个数组
- 当数组存在并有值时,如果数组中的任何值发生更改,则每次渲染后都会触发回调。
- 当它不存在时,每次渲染后都会触发回调。
- 当它是一个空列表时,回调只会被触发一次,类似于 componentDidMount。
示例:新建一个定时器,每秒数量+1
传统的写法:
import React, { Component } from 'react';
class Count extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 1,
}
}
componentDidMount() {
// 新建一个定时器实现+1操作
this.timer = setInterval(() => {
this.setState({
count: this.state.count - 0 + 1
})
}, 1000);
}
componentWillUnmount() {
// 组件销毁时清除定时器,否则会造成内存泄漏
clearInterval(this.timer)
}
render() {
return <div>
{this.state.count}
</div>
}
}
export default Count;
hooks的写法:
import React, { useState, useEffect } from 'react';
const Count = (props) => {
const [count, setCount] = useState(0);
useEffect(() => {
let timer = setInterval(() => {
setCount(count + 1)
}, 1000);
// 当在 useEffect 的回调函数中返回一个函数时,这个函数会在组件卸载前被调用
return () => clearInterval(timer)
// count发生变化时再次执行
}, [count]);
return <div>
{count}
</div>
}
export default Count;
3.useRef
useRef
返回一个可变的 ref 对象,其 .current
属性被初始化为传入的参数(initialValue
)。返回的 ref 对象在组件的整个生命周期内保持不变。
示例:获取某个元素
传统写法:
import React, { Component } from 'react'
export default class Test extends Component {
componentDidMount() {
console.log(this.refs.box)
}
render() {
return (
<div ref="box">
this is a box
</div>
)
}
}
hooks写法:
import React, { useRef, useEffect } from "react";
export default function Test() {
// 初始化一个useRef
const box = useRef(null);
useEffect(() => {
// 通过.current获取
console.log(box.current)
}, [])
return (
<div ref={box}>this is a box</div>
);
}
4.useContext
接收一个 context 对象,并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider>
的 value
prop 决定。
示例:父组件传值“Text”给子组件
传统方式:
//父组件
<Child Text="text" />
//子组件
export default class Child extends PureComponent {
constructor(props) {
super(props);
this.state = {
'Text': props.CouponChannelID,
};
}
}
hooks方式:
//父组件
import React from "react";
export const TextContext = React.createContext({});
<TextContext.Provider value={{
text: "this is a text"
}}>
<Child></Child>
</TextContext.Provider>
//子组件接收
import React, { useContext } from "react";
import { TextContext } from './parent';
export default function MemberPop() {
const { text } = useContext(TextContext);
}
使用useSelector useDispatch 替代connect
React Redux 现在提供了一系列 hook APIs 作为现在connect()高阶组件的替代品。这些 APIs 允许你,在不使用connect()包裹组件的情况下,订阅 Redux 的 store,和分发(dispatch) actions。
1.useSelector 从redux的store对象中提取数据
2.useDispatch 返回Redux store中对dispatch函数的引用
传统写法:
import React, { PureComponent, Fragment } from "react";
import { connect } from "react-redux";
import actions from "@/store/actions";
@connect(
state => state.getData,
actions
)
export default class BackTopWrap extends PureComponent {
componentDidMount() {
const {
getShowAction,
} = this.props;
getShowAction();
}
render() {
const { show } = this.props;
return (
<div className="backTop" style={{ "display": show ? "block" : "none" }}>
);
}
}
hooks写法:
import React, { Fragment, useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { getShowAction} from '@/store/actions';
export default function OpenedPage() {
const { show } = useSelector(state => state.getData);
const dispatch = useDispatch();
useEffect(() => {
getShowAction(dispatch);
}, [])
return (
<div className="backTop" style={{ "display": show ? "block" : "none" }}>
);
}
自定义hooks:
自定义 Hook 是一个函数,其名称以 “use
” 开头,函数内部可以调用其他的 Hook。
示例:封装一个组件,实现鼠标滑过显示鼠标所在位置
传统方式:定义一个高阶组件,使用时将组件作为参数传入高阶组件
/**
* 定义一个高阶组件
* 返回鼠标所在位置
*/
import React from 'react'
export default (Component) => {
return class WrappedComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
positionX: 0,
positionY: 0
}
}
componentDidMount() {
document.addEventListener('mousemove', (e) => {
this.setState({
positionX: e.clientX,
positionY: e.clientY
})
}) // 在这里我们更新鼠标的位置,并存储在state中去,然后通过props传递给被传入的组件
}
render() {
return (
<Component {...this.props} {...this.state} {...{ 'x': 1 }} />
//props:这里返回的是WrappedComponent这个组件,所以本应该传递给Component组件的props,我们应该通过WrappedComponent传递下去
//state: WrappedComponent可以操作自己的状态,我们可以将这些状态通过props的方式传递给Component组件
)
}
}
}
/**
* 使用时将组件作为参数传入高阶组件
*/
import React from 'react';
import mousePositionHoc from './hoc';
class MousePoint extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
<span>x:{this.props.positionX}</span>
<span>y:{this.props.positionY}</span>
</div>
)
}
}
export default mousePositionHoc(MousePoint)
hooks:自定义一个hooks,使用时直接引入。
/**
* 自定义一个hooks
* 返回鼠标所在位置
*/
import React, { useState, useEffect } from 'react'
const useMousePosition = () => {
const [position, setPosition] = useState({ x: 0, y: 0 })
useEffect(() => {
const updateMouse = (e) => {
setPosition({ x: e.clientX, y: e.clientY })
}
document.addEventListener('mousemove', updateMouse)
}, [])
return position
}
export default useMousePosition
//使用时引入自定义hooks
import React from 'react'
import useMousePosition from './hooks'
export default function App() {
const position = useMousePosition()
return (
<div>
<div>x: {position.x}</div>
<div>y: {position.y}</div>
</div>
)
}