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 中直接访问countstate 变量(或其他 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'))