Hooks
Hooks 是 React 中的核心
函数式组件中存在的问题:
- 必须是纯函数
- 不能包含状态(state)
- 不支持生命周期(没有继承,无法继承自 React.Component)
纯函数:一个函数的返回结果,只依赖于自己的参数,并且执行的过程中没有副作用
副作用: 除了干自己的主要任务以外,还做了其他的事情。当一个函数调用的时候,除了返回这个函数值以外,还对主调用函数产生了附加的影响
// 纯函数
let x = 1;
function add(a, b) {
return a + b;
}
// 不是纯函数,x 是外部变量,依赖外部变量,而不是自己的参数
console.log(add(x, 10));
const obj = {};
function fun(b) {
obj.a = 10;
return b;
}
// 不是纯函数,改变了全局作用域的 obj,产生了副作用
console.log(fun(5), obj);
// 纯函数
let a = 1;
let b = 2;
/*
1. 他没有改变外部变量的值
2. 每次调用的时候,传入的值相同,每次返回的结果也相同
相同的输入得到相同的输出
*/
function sum(x, y) {
return x + y;
}
console.log(sum(a, b));
函数式编程不是不需要副作用,只是在需要的时候限制副作用
类组件存在的问题:
1.代码很重
2.每次创建的组件都要继承 React.Component 实例
函数组件存在问题:
1.纯函数没有状态
2.纯函数没有生命周期
3.纯函数没有 this
4.只能是纯函数
函数式组件只能用作 UI 展示,涉及到状态的管理只能使用 类组件
因此就是想用纯函数组件,并且可以进行对应的状态管理,因此 react 设计团队,设计了 hooks
组件尽量写成纯函数,如果需要外部 的数据或者是功能,准备一把钩子,把涉及到的数据和功能的代码勾进纯函数组件里面
useState
useState 定义
声明状态变量:
import React, { useState } from 'react';
export default function App() {
const arr = useState(0);
console.log(arr[0], arr[1]);
/* 打印结果 0 ƒ dispatchSetState(fiber, queue, action) {
{
if (typeof arguments[3] === 'function') {
error("State updates from the useState() and useReducer() Hooks don't support the " + 'sec…
*/
/*
useState(0)
useState会返回一个数组
第一元素是当前的状态
第二个元素是 一个函数,这个函数的作用就是设置修改状态
*/
const state = arr[0];
const setState = arr[1];
return (
<div>
<h2>{state}</h2>
<button onClick={() => setState(state + 1)}>+1</button>
</div>
);
}
useState 基本使用
import React, { useState } from 'react';
export default function App() {
// 声明一个 state 的变量,赋值初始化,只在首次渲染的时候使用,如果去更新的话这个会被忽略
const [state, setState] = useState(0);
return (
<div>
<h1>{state}</h1>
<button onClick={() => setState(state + 1)}>+1</button>
</div>
);
}
函数式更新:
import React, { useState } from 'react';
export default function App() {
// 声明一个 state 的变量,赋值初始化,只在首次渲染的时候使用,如果去更新的话这个会被忽略
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
return (
<div>
<h1>{count}</h1>
<button onClick={increment}>+1</button>
</div>
);
}
多个 useState
import React, { useState } from 'react';
export default function App() {
// 声明一个 state 的变量,赋值初始化,只在首次渲染的时候使用,如果去更新的话这个会被忽略
const [money, setMoney] = useState(10000);
const [stars, setStars] = useState(['迪丽热巴', '杨幂', '沈梦瑶']);
const [people, setPeople] = useState({ name: '张三', age: 18 });
return (
<div>
<h1>{money}</h1>
<button onClick={() => setMoney(money + 1)}>加钱</button>
<ul>
{stars.map((item) => (
<li key={item}>{item}</li>
))}
{/*
setStars 去更新的时候,注意新旧之不能相等
像数组,对象引用数据类型需要拷贝为新对象才能触发更新
*/}
<button onClick={() => setStars([...stars, '袁一琦'])}>加人</button>
</ul>
<h2>{people.name}</h2>
<button onClick={() => setPeople({ ...people, name: '李四' })}>
修改名字
</button>
</div>
);
}
import React, { useState } from 'react';
export default function App() {
const [staff, setStaff] = useState([
{ id: 1, name: '张三', salary: 180000 },
{ id: 2, name: '李四', salary: 180000 },
{ id: 3, name: '王五', salary: 180000 },
]);
const change = (index) => {
// 浅拷贝
let newStaff = [...staff];
newStaff[index].salary += 10000;
setStaff(newStaff);
};
return (
<div>
<ul>
{staff.map((item, index) => {
return (
<li key={item.id}>
<span>员工姓名:{item.name}</span>
<span>员工薪水:{item.salary}</span>
<button
onClick={() => {
change(index);
}}
>
加工资
</button>
</li>
);
})}
</ul>
</div>
);
}
import React, { useState } from 'react';
const Son = (props) => {
let [state, setState] = useState(() => {
if (props.init > 60) {
console.log('大于60');
return 60 * 60;
}
});
return (
<div>
<h1>son组件</h1>
<p>{state}</p>
<button
onClick={() => {
setState(state + 10);
}}
>
增加10
</button>
</div>
);
};
export default function App() {
return (
<div>
<Son init={70} />
</div>
);
}
useState 注意点:
- 初始参数值还在组件的首次渲染的时候使用,再次更新时会被忽略
- 每次通过 setXXX 修改状态都会引起组件重新渲染
- 可以调用多次,每次都是独立的
- useState 只可以在函数组件之内使用
- 如果初始值需要计算才能得到,可以使用回调函数的形式来确定初始值
useEffect
useEffect:可以为 React 组件完成副作用的操作
一个纯函数组件主要的功能是通过数据渲染的 UI,剩余的其他的操作都是副作用
1.ajax 请求
2.手动 DOM
3.本地存储
useEffect 的第二个参数,是依赖变量,是一个数组,可以有多个依赖项,一旦这一个依赖项发生了汴东,这个 useEffect 就会重新执行
// 组件对应的某个时段做什么操作 => 生命周期函数
import React, { useEffect, useState } from 'react';
const Children = () => {
useEffect(() => {
return () => {
console.log('组件被销毁了');
};
}, []);
return (
<div>
<h2>children组件</h2>
</div>
);
};
export default function App() {
const [count, setCount] = useState(0);
const [money, setMoney] = useState(100);
const [flag, setFlag] = useState(false);
// 副作用操作 DOM
useEffect(() => {
console.log('componentDidMount执行了');
// 依赖为空的时候只在页面初始的时候只执行一次
}, []);
useEffect(() => {
console.log('componentDidUpdate执行了');
//第二个参数是依赖数组 只有count变化时才执行,money 变化时不执行
}, [count]);
useEffect(() => {
console.log('componentDidMount执行了');
// 没有依赖的时候每次都会执行
});
return (
<div>
<h1>{count}</h1>
<h2>{money}</h2>
<button onClick={() => setCount(count + 1)}>增加+1</button>
<button onClick={() => setMoney(money + 100000)}>加钱</button>
{flag && <Children />}
<button onClick={() => setFlag(!flag)}>切换</button>
</div>
);
}
import React, { useEffect, useState } from 'react';
import axios from 'axios';
export default function App() {
let [list, setList] = useState([]);
// 发起请求
useEffect(() => {
const initData = async () => {
const { data } = await axios.get(
'https://www.fastmock.site/mock/2728fdedd7e9063e308598df4c68fe46/_api/api/list'
);
console.log(data);
setList(data.data);
};
initData();
}, []);
return (
<div>
<ul>
{list.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
useEffect 的清除作用:
import React, { useEffect, useState } from 'react';
/* 子组件 */
function Child() {
const [count, setCount] = useState(0);
/*
开启和清除定时器
*/
useEffect(() => {
const timer = setInterval(() => {
setCount((count) => count + 1);
}, 1000);
// 清楚定时器 return 是 Effect 的一个可选的清除机制,每一个 uesEffect 都有一个返回一个清除函数
// return 执行机制,是在组件被销毁之前才会执行
// 做清除副作用的操作
return () => {
clearInterval(timer);
console.log('定时器消除了—— 组件销毁了!');
};
}, []);
return (
<div>
<h2>{count}</h2>
<h1>Child</h1>
</div>
);
}
export default function App() {
const [display, setDisplay] = useState(true);
return (
<div>
{display && <Child />}
<button onClick={() => setDisplay(!display)}>
{display ? '隐藏child' : '显示child组件'}
</button>
</div>
);
}
依赖项执行时机:
1.默认状态:首次执行,组件渲染初次的时候执行+组件更新执行 useEffect( ) 函数
2.如果依赖项为[],那么只执行一次,在组建首次渲染的时候执行,只执行一次
3.添加特定的依赖项的时候,首次执行 + 依赖项发生更新的时候执行
useContext
定义:帮助跨组件直接传递,实现跨级组件通信
使用的方法:
- 使用 createContext 创建一个 Context 对象
- 在顶层组件 通过 Provider 提供数据
- 底层组件通过 useContext 函数去获取数据
import React, { useState, useContext, createContext } from 'react';
// 创建一个 context
const context = createContext();
// 孙子组件
function GrandSon() {
const { count, setCount, num, setNum } = useContext(context);
return (
<div>
<h1>GrandSon 组件</h1>
<div>{count}</div>
<div>{num}</div>
<button onClick={() => setCount(count + 1)}>count + 1</button>
<button onClick={() => setNum(num + 1)}>num + 1</button>
</div>
);
}
// 孙子组件
function Son() {
return (
<div>
<GrandSon />
</div>
);
}
function Father() {
const [count, setCount] = useState(0);
const [num, setNum] = useState(10);
return (
<div>
<context.Provider value={{ count, setCount, num, setNum }}>
<Son />
</context.Provider>
</div>
);
}
export default function App() {
return (
<div>
<Father />
</div>
);
}
useRef
主要功能是 获取 DOM 元素或者组件,还可以保存值
使用 useRef 获取 DOM 元素:
- 引入 useRef,调用 useRef( )
- 通过 ref 属性将 useRef 的返回值绑定到 元素身上
- useRef( ) 返回的是一个对象,对象内有一个 current 属性
import React, { useRef, useEffect } from 'react';
class Header extends React.Component {
constructor() {
super();
this.state = {
name: '张三',
};
}
render() {
return (
<div>
<h2>Header 组件</h2>
</div>
);
}
say = () => {
console.log('我是 Header 组件方法 say');
};
}
export default function App() {
const inputRefs = useRef();
const useRefs = useRef();
const headerRefs = useRef();
// 副作用获取DOM,一进入页面获取焦点
useEffect(() => {
inputRefs.current.focus();
});
const changeText = () => {
console.log(headerRefs);
console.log(headerRefs.current.state.name);
headerRefs.current.say(); //我是 Header 组件方法 say
// 获取修改 h2 标签的文本内容
useRefs.current.innerHTML = inputRefs.current.value;
};
return (
<div>
<Header ref={headerRefs} />
<h2 ref={useRefs}>H2标签</h2>
<input type='text' ref={inputRefs} />
<button onClick={() => changeText()}>按钮</button>
</div>
);
}
useRef
主要功能是 获取 DOM 元素或者组件,还可以保存值
使用 useRef 获取 DOM 元素:
- 引入 useRef,调用 useRef( )
- 通过 ref 属性将 useRef 的返回值绑定到 元素身上
- useRef( ) 返回的是一个对象,对象内有一个 current 属性
import React, { useRef, useEffect } from 'react';
class Header extends React.Component {
constructor() {
super();
this.state = {
name: '张三',
};
}
render() {
return (
<div>
<h2>Header 组件</h2>
</div>
);
}
say = () => {
console.log('我是 Header 组件方法 say');
};
}
export default function App() {
const inputRefs = useRef();
const useRefs = useRef();
const headerRefs = useRef();
// 副作用获取DOM,一进入页面获取焦点
useEffect(() => {
inputRefs.current.focus();
});
const changeText = () => {
console.log(headerRefs);
console.log(headerRefs.current.state.name);
headerRefs.current.say(); //我是 Header 组件方法 say
// 获取修改 h2 标签的文本内容
useRefs.current.innerHTML = inputRefs.current.value;
};
return (
<div>
<Header ref={headerRefs} />
<h2 ref={useRefs}>H2标签</h2>
<input type='text' ref={inputRefs} />
<button onClick={() => changeText()}>按钮</button>
</div>
);
}
使用 useRef 保存值:
// 使用 useRef 保存值
import React, { useRef, useState } from 'react';
export default function App() {
const [count, setCount] = useState(0);
const countRef = useRef(count);
return (
<div>
<h1>count:{count}</h1>
<h1>countRef:{countRef.current}</h1>
{/* count 会通过点击 +1 但是 useRef 中的值不会发生变化,所以可以用来保存数据 */}
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
useReducer
useReducer 是一个高级 Hook,(useState,useEffect,useRef 是必须的 hook)
useState 提供组件的状态,useReducer 是 useState 升级版,所有的 useState 规则,useReducer 都适用
基本使用:
import React, { useReducer } from 'react';
// 初始化状态
const initState = 10;
/* reducer 函数 */
/*
1. reducer 函数是 state 的处理函数
2. state:用于获取state状态的值
3. action:用于修改 state 的派发器
一种用于分配事件处理的机制
通过action 发送操作 state 的指令
具体的修改行为由 reducer 执行
接收当前的 state 和触发的动作的 action ,计算并返回最新的 state
*/
function reducer(state, action) {
switch (action.type) {
case 'increment':
return state + 1;
case 'decrement':
return state - 1;
default:
throw new Error('unknow action');
}
}
export default function App() {
// useReducer 接受两个参数,第一个是 reducer 函数 第二个参数,初始值
// const arr = useReducer(reducer, initState); // 0 function
// dispatch 接受一个参数,执行对应的 action,dispatch 执行以后,对应的 state 会改变,组件会重新渲染,显示最新的 state
const [state, dispatch] = useReducer(reducer, initState); // 0 function
// console.log(arr); // [0, ƒ]
return (
<div>
<h1>initState:{state}</h1>
{/* dispatch 用来接收一个 action 函数,reducer 函数中的 action ,用来触发reducer 函数,用来触发 reducer 函数,更新最新的状态*/}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
useReducer 的使用步骤:
- 定义一个 initState
- 定义一个 reducer 函数,把所有的操作方法都放在这个函数里面
- 把 initState 跟 reducer,通过 useReducer 关联起来,返回一个当前的 state 和 dispatch
- 当需要计算的时候,使用 dispatch 传递一个 action 值,触发的 reducer 函数,返回一个新的 state
- useReducer 数据不共享,共享的事 reducer 函数,redux
import React, { useReducer } from 'react';
const initState = {
name: '',
age: '',
hobby: '',
};
function reducer(state, action) {
switch (action.type) {
case 'change':
// 传入 inputvlue 中的值赋给 initState
return { ...state, ...action.inputValue };
default:
break;
}
}
export default function App() {
const [state, dispatch] = useReducer(reducer, initState);
const click = () => {
console.log(
`姓名:${state.name},年龄:${state.age},爱好:${state.hobby}`
);
};
return (
<div>
<form>
<p>
姓名:
<input
onChange={(e) => {
dispatch({
type: 'change',
inputValue: { name: e.target.value },
});
}}
></input>
</p>
<p>
年龄:
<input
onChange={(e) => {
dispatch({
type: 'change',
inputValue: { age: e.target.value },
});
}}
></input>
</p>
<p>
爱好:
<input
onChange={(e) => {
dispatch({
type: 'change',
inputValue: { hobby: e.target.value },
});
}}
></input>
</p>
<button onClick={click} type='button'>
确认
</button>
</form>
</div>
);
}
import React, { useReducer } from 'react';
// 初始化状态
const initState = 0;
/* reducer 函数 */
/*
1. reducer 函数是 state 的处理函数
2. state:用于获取state状态的值
3. action:用于修改 state 的派发器
一种用于分配事件处理的机制
通过action 发送操作 state 的指令
具体的修改行为由 reducer 执行
接收当前的 state 和触发的动作的 action ,计算并返回最新的 state
*/
function reducer(state, action) {
switch (action.type) {
case 'increment':
return state + 1;
case 'decrement':
return state - 1;
default:
throw new Error('unknow action');
}
}
// 父组件
function Father() {
const [state, dispatch] = useReducer(reducer, initState);
return (
<div>
<h1>父组件{state}</h1>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</div>
);
}
// 子组件
function Son() {
const [state, dispatch] = useReducer(reducer, initState);
return (
<div>
<h1>子组件{state}</h1>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
export default function App() {
return (
<div>
<Father />
<Son />
</div>
);
}
useCallback
解决了重复渲染的问题,性能优化相关:
import React, { useState, memo } from 'react';
// function Son() {
// console.log('渲染了');
// return (
// <div>
// <h2>Son 组件</h2>
// </div>
// );
// }
// memo 可以解决组件的重复渲染
// 高阶组件 将组件作为一个参数并且返回一个新的组件
const Son = memo(function (props) {
console.log('Son渲染了'); //传递 function 过来的时候 依然重复渲染了
return (
<div>
<h2>Son 组件</h2>
<h2>{props.name}</h2>
</div>
);
});
export default function App() {
const [count, setCount] = useState(0);
const [name, setName] = useState('张三');
const change = (name) => {
setName(name);
};
return (
<div>
<h1>{count}</h1>
{/* 每次调用 setCount 都会重新渲染父组件,从而渲染子组件 */}
<button onClick={() => setCount(count + 1)}>Add</button>
<Son name={name} onClick={change} />
</div>
);
}
function App() {
let n = 0;
return <div></div>;
}
每次调用的都是 不同的 n,因为每次都会产生新的局部变量,所以每次 change 执行的时候,会产生一个新的 change
useCallback 起到了一个缓存的作用,即便父组件重新渲染了,useCallback 包裹的函数不会重新生成,会返回上一次函数的引用,需要 memo 配合 useCallback 使用
export default function App() {
const [count, setCount] = useState(0);
const [name, setName] = useState('张三');
const change = useCallback((name) => {
setName(name);
}, []);
return (
<div>
<h1>{count}</h1>
{/* 每次调用 setCount 都会重新渲染父组件,从而渲染子组件 */}
<button onClick={() => setCount(count + 1)}>Add</button>
<Son name={name} onClick={change} />
</div>
);
}
useMemo
与 useCallback 功能一样防止重复渲染
父组件渲染,会生成一个新的对象,导致传递给子组件的 person 属性发生了变化,进而导致子组件会重新渲染,浅比较
import React, { memo, useState, useMemo } from 'react';
const Son = memo(function (props) {
console.log(props);
console.log('son 组件 render.....');
return (
<div>
<h1>Son组件 </h1>
</div>
);
});
export default function App() {
const [count, setCount] = useState(0);
const person = {
name: '张三',
age: 18,
};
/*
1. 第一个参数:一个函数,返回的对象指向同一个引用,不会创建新的对象
2. 第二个参数是一个数组,只有数组的变量改变才会使第一函数返回一个新的对象,与 useEffect 相似
*/
const data = useMemo(() => {
return { name: '张三', age: 18 };
}, []);
return (
<div>
{/* <Son person={person} /> */}
<Son data={data} />
<button onClick={() => setCount(count + 1)}>{count}</button>
</div>
);
}