4. REACT中常用的HOOK

180 阅读6分钟

React Hooks:创建REACT组件的新方式(给函数式组件提供各种钩子函数,让其也具备类组件中的一些特点)

介于函数式组件和类组件之间,即能像函数式组件一样开发和渲染快,也能像类组件一样,有自己的状态和生命周期等

1.useState

想在函数式组件中拥有状态和修改状态后重新渲染组件的方法

语法:let [状态STATE,修改状态的方法SET-STATE] = useState(初始状态值);

SET-STATE(N) 把STATE修改成为N(重新渲染视图)

import React, { useState } from 'react';
import ReactDOM from 'react-dom';

function App(props) {
    let title = props.title || '';
    let [supNum, handleSupNum] = useState(0);
    let [oppNum, handleOppNum] = useState(0);
    return <div>
        <h2>{title}<span>{supNum+oppNum}</span></h2>
        <p>支持人数:{supNum}</p>
        <p>反对人数:{oppNum}</p>
        <button onClick={ev=>{
            handleSupNum(supNum+1)
        }}>支持</button>
        <button onClick={ev=>{
            handleOppNum(oppNum+1)
        }}>反对</button>
    </div> 
}
ReactDOM.render(<App title="今天天气真好"></App>, document.getElementById('root'))

尝试使用引用类型值管控多状态

1.SET-STATE中赋值什么,都相当于直接把原始状态整体替换成什么,所以在设置新状态信息之前,我们需要把原始状态克隆一份过来

setState({...state,xxx:xxx})

2.每一次重新赋值状态,都会额外多开辟一些内存空间,所以官方推荐我们使用多次 USE-STATE

import React, { useState } from 'react';
import ReactDOM from 'react-dom';

function App(props) {
    let title = props.title || '';
    let [state, handle] = useState({
        oppNum: 0,
        supNum: 0
    });
    /*let [state, setState] = useState(function () {
    //=>initialState设置成为函数,可以保证每一次组件重新渲染的时候,无需再重新初始化状态了 =>惰性初始化
		return {
			supNum: 0,
			oppNum: 0
		}
	});
	*/
    let { oppNum, supNum } = state;
    return <div>
        <h2>{title}<span>{supNum + oppNum}</span></h2>
        <p>支持人数:{supNum}</p>
        <p>反对人数:{oppNum}</p>
        <button onClick={ev => {
            handle({
                ...state,
                supNum: supNum + 1
            })
        }}>支持</button>
        <button onClick={ev => {
            handle({
                ...state,
                oppNum: oppNum + 1
            })
        }}>反对</button>
    </div>
}
ReactDOM.render(<App title="今天天气真好"></App>, document.getElementById('root'))

原理

*useState 需要哪些参数? useState() 方法里面唯一的参数就是初始 state。不同于 class 的是,我们可以按照需要使用数字或字符串对其进行赋值,而不一定是对象

*useState 方法的返回值是什么? 返回值为:当前 state 以及更新 state 的函数。

我们声明了一个叫 count 的 state 变量,然后把它设为 0。React 会在重复渲染时记住它当前的值,并且提供最新的值给我们的函数。我们可以通过调用 setCount 来更新当前的 count

因为 state 只在组件首次渲染的时候被创建。在下一次重新渲染时,useState 返回给我们当前的 state。

let _state;
function useState(initialState) {
	!_state ? _state = initialState : null;
	function dispatchAction(new_state){
		_state = new_state;
		// render...
	}
	return [state, dispatchAction]
}

2.useEffect

类似于componentDidMount/Update

在class 中,我们可能需要在两个生命周期函数中编写重复的代码

我们希望在组件加载和更新时执行同样的操作。从概念上说,我们希望它在每次渲染之后执行 —— 但 React 的 class 组件没有提供这样的方法。即使我们提取出一个方法,我们还是要在两个地方调用它。

React 会保存你传递的函数(我们将它称之为 “effect”),并且在执行 DOM 更新之后调用它。

为什么在组件内部调用 useEffect?useEffect 放在组件内部让我们可以在 effect 中直接访问 count state 变量(或其他 props)。我们不需要特殊的 API 来读取它 —— 它已经保存在函数作用域中。

useEffect 会在每次渲染后都执行吗? 是的,默认情况下,它在第一次渲染之后每次更新之后都会执行。

React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。

import React, { useState, useEffect } from 'react';

export default function Vote(props) {
	let title = props.title || '--';
	//=>使用状态
	let [state, setState] = useState(()=>{
		({
			supNum: 0,
			oppNum: 0
		})
	});
	let { supNum, oppNum } = state;

	//=>使用生命周期USE-EFFECT
	/* useEffect(() => {
		//=>每一次渲染完(不管第一次或者重新渲染)都会触发执行 <=> componentDidMount/componentDidUpdate
		console.log('OK');
	}); */
	/* useEffect(() => {
		//=>指定依赖项:只有数组中的状态发生改变,才会触发这个函数执行
		// 数组中有多项,只要有一项改变则会执行
		console.log('OK');
	}, [supNum, oppNum]); */
	/* useEffect(() => {
		//=>如果传递的是空数组,则只有第一次渲染完成后执行 =>componentDidMount
		console.log('OK');
	}, []); */
	return <div>
		<h4>{title} <span>N:{supNum + oppNum}</span> </h4>
		<p>支持人数:{supNum}</p>
		<p>反对人数:{oppNum}</p>
		<button onClick={ev => {
			setState({
				...state,
				supNum: supNum + 1
			});
		}}>支持</button>
		<button onClick={ev => {
			setState({
				...state,
				oppNum: oppNum + 1
			});
		}}>反对</button>
	</div>;
};

原理

let prev = [];
function useEffect(callback, dependencyList) {
    // 如果传递的是有内容的数组,
	let flag = (dependencyList && dependencyList.length > 0) ? dependencyList.some((item, index) => {
		return item !== prev[index]; //true
	}) : (prev.length === 0 ? true : false);  // 如果是空数组,为true

	if (!dependencyList || flag) {  // 不传或者flag = true的时候执行
		callback();
        // 如果是空数组,给prev赋值,确保只执行一次
		prev = dependencyList.length === 0 ? ["@@@"] : dependencyList;
	}
} 

3.createRef / useRef

都可以获取到DOM元素

createRef 和 useRef 的区别:

  • createRef每一次重新渲染组件都会创建一个全新的REF对象
  • useRef第一次渲染创建一个对象,之后重新渲染的时候,如果发现已经创建过,则不再重新创建,性能要比createRef好一些
import React, { createRef, useRef, useEffect, useState } from 'react';

let prev;
export default function InputBox(props) {
/*
* inputRef = {current:存储DOM元素对象} 
*    createRef()刚创建current=null
*    当组件渲染的时候会识别 ref={inputRef} 把当前元素获取到赋值给current,所以在渲染完成后 inputRef.current 可以获取对应的DOM对象
*/
	const [num, changeNum] = useState(0);
	const inputRef = useRef();

	return <div>
		{num}
		<input type="text" ref={inputRef} />
		<button onClick={ev => {
			inputRef.current.focus();
			changeNum(num + 1);
		}}>自动聚焦</button>
	</div>;
};

4.useReducer

vue VS React对比

import React, { useReducer } from 'react';
import ReactDOM from 'react-dom'

//=>初始状态
let initailState = {
	n: 0,
	m: 0
};
//=>统一修改STATE的函数
// state容器中当前的状态
// actions基于dispatch派发的行为操作(它就是派发时候传递的对象)
function reducer(state, action) {
	console.log(state,action)  
	switch (action.type) {
		case 'CHANGE-N':
			state = { ...state, n: action.payload };
			break;
		case 'CHANGE-M':
			state = { ...state, m: action.payload };
			break;
	}
	return state; //=>返回啥就把当前容器中的状态改成啥
}

export default function ReducerBox() {
	let [state, dispatch] = useReducer(reducer, initailState);
	let { n, m } = state;
	let total = parseFloat(n) + parseFloat(m);
	total = isNaN(total) ? 0 : total;
	return <div>
		<input type="text" value={n} onChange={ev => {
			dispatch({
				type: 'CHANGE-N',
				payload: ev.target.value
			});
		}} />
		+
		<input type="text" value={m} onChange={ev => {
			dispatch({
				type: 'CHANGE-M',
				payload: ev.target.value
			});
		}} />
		=
		<span>{total}</span>
	</div>;
}
ReactDOM.render(<ReducerBox title="今天天气真好"></ReducerBox>, document.getElementById('root'))