HOOKS学习笔记
类组件与函数组件的区别
类组件可以定义自己的state,用来保存自己的内部状态,类组件有自己的生命周期,可以在对应的生命周期中完成对应的逻辑操作。在componentDidMount发送网络请求,在数据更新时可以调用componentDidUpdate生命周期,在状态发生改变只会重新执行render函数
类组件中产生的副作用需要在componentWillUmmount中清除,比如redux中手动调用subscribe
函数组件不可以,函数每次调用都会产生新的临时变量,函数组件被重新渲染时,整个函数都会被重新执行
memo的作用:类似于类组件的pureC,会对props进行浅层比较,如果发生更新就重新渲染,如果没有发生更新就不会重新渲染
hooks
需要在react中导入
使用hooks的原则
只能在函数最外层调用Hook。不要在循环、条件判断或者子函数中调用。
只能在React 的函数组件中调用Hook。不要在其他JavaScript 函数,如果需要做条件判断,则可以将判断放到hook的内部
useState
用于保存状态
// 声明一个叫 “count” 的 state 变量。
const [count, setCount] = useState(0);
const [firends, setFirends] = useState(['kobe', 'mar']);
useState的唯一参数就是初始state,
返回值是一个数组(当前state的值,设置新的值时使用的函数)
调用setState会做两件事情:设置新的值,组件重新渲染,根据新的值返回DOM结构
setState可以传入对象 | 函数,实现结果同于setState({})
每次更改状态做的都是替换的操作
添加用户和修改用户年龄
import React, { useState } from 'react';
export default function ComplexHookState() {
const [friends, setFrineds] = useState(['kobe', 'lilei']);
const [students, setStudents] = useState([
{ id: 110, name: 'why', age: 18 },
{ id: 111, name: 'kobe', age: 30 },
{ id: 112, name: 'lilei', age: 25 },
]);
const [person,setPerson] = useState({name:'李四',age:20})
const incrementAgeWithIndex = index => {
const newStudents = [...students];
newStudents[index].age += 1;
setStudents(newStudents);
};
const addFriends = () => {
let newFriend = [...friends, 'tom'];
setFrineds(newFriend);
};
return (
<div>
<h2>好友列表:</h2>
<ul>
{friends.map((item, index) => {
return <li key={index}>{item}</li>;
})}
</ul>
<button onClick={addFriends}>添加朋友</button>
<h2>学生列表</h2>
<ul>
{students.map((item, index) => {
return (
<li key={item.id}>
<span>
名字: {item.name} 年龄: {item.age}
</span>
<button onClick={e => incrementAgeWithIndex(index)}>age+1</button>
</li>
);
})}
</ul>
<button onClick = {() => setPerson({name:'王五',age:30})}>setPerson</button>
</div>
);
}
useEffect
完成类组件中生命周期的功能:比如网络请求,手动更新DOM,事件监听。useEffect接收两个参数,第二个参数为可选参数,第一个参数为回调函数(在发生更新后就执行回调函数),在完成DOM更新后执行回调。
// 相当于 componentDidMount 和 componentDidUpdate:
useEffect(() => {
// 使用浏览器的 API 更新页面标题
document.title = `You clicked ${count} times`;
});
订阅与取消订阅操作
组价被渲染时自动执行useEffect,当组件被销毁是执行useEffect中的return
import React, { useState, useEffect } from 'react';
export default function MultiEffectHookDemo() {
const [count, setCount] = useState(0);
const [isLogin, setIsLogin] = useState(true);
// 当count改变了执行当前effect
useEffect(() => {
console.log('修改DOM', count);
}, [count]);
useEffect(() => {
console.log('订阅事件');
return () => {
console.log('取消订阅事件');
};
}, []);
// 将isLogin作为effect依赖项,当isLogin发生改变则执行网络请求
useEffect(() => {
console.log('网络请求');
}, [isLogin]);
const setCounter = () => {
setCount(count + 1);
};
const setLogin = () => {
setIsLogin(!isLogin);
};
return (
<div>
<h2>MultiEffectHookDemo</h2>
<h2>{count}</h2>
<button onClick={setCounter}>+1</button>
<h2>{isLogin ? 'coderwhy' : '未登录'}</h2>
<button onClick={e => setIsLogin(!isLogin)}>注册/登录</button>
<button onClick={setLogin}>登录/注销</button>
</div>
);
}
useEffect中返回一个函数:effect的可选清除机制,每一个effect都可以返回一个清除函数。react会在卸载的时候执行清除操作。
第二个参数:传入数组类型(性能优化),监听依赖变量改变后再执行hook中的回调函数
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
如果仅传入空数组,不传入依赖变量,则useEffect只会执行一次
useContext
接收一个 context 对象(React.createContext
的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider>
的 value
prop 决定。
当组件上层最近的 <MyContext.Provider>
更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext
provider 的 context value
值
父组件
创建context对象
import React, { createContext } from 'react';
export const UserContext = createContext();
<UserContext.Provider value={{name:'byj'}}>
<ContextHookDemo/>
</UserContext.Provider>
子组件
import React, { useContext } from 'react';
import {UserContext} from "../App"
通过user拿到父组件共享过来的数据(对应的就是父组件穿件的context对象)
const user = useContext(UserContext)
useReducer
是useState的一种替代方案,主要是在处理逻辑复杂用于拆分,或者这次修改的state需要依赖之前的state时使用
reducer共享的仅仅是操作函数,数据不会共享
home.js
import React, { useReducer } from 'react';
import reducer from './reducer';
export default function Home() {
const [state, dispatch] = useReducer(reducer, { counter: 0 });
const addCount = () => {
dispatch({
type: 'increment',
});
};
return (
<div>
<h2>Home当前计数: {state.counter}</h2>
{/* <button onClick={e => dispatch({ type: 'increment' })}>+1</button> */}
<button onClick={addCount}>+1</button>
<button onClick={e => dispatch({ type: 'decrement' })}>-1</button>
</div>
);
}
reducer.js
export default function reducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, counter: state.counter + 1 };
case 'decrement':
return { ...state, counter: state.counter - 1 };
default:
return state;
}
}
userCallbcak
用途:性能优化。传入一个回调函数返回执行后的函数,在依赖不改变或者没有添加依赖的情况下,返回的永远是同一个值
使用场景:将组件中的函数传递给子组件进行回调使用时,使用useCallback对函数进行处理,useCallback直接对值进行优化
useMemo
用途:性能优化,返回一个memoized值(可以是对象或函数等),在依赖不改变或者没有添加依赖的情况下,返回的永远是同一个值
useMemo传入一个回调函数,返回一个值作为usememo的返回值,可以传入依赖项,当依赖项发生改变后函数才会重新执行一次
场景: 在将一个组件中的函数, 传递给子元素进行回调使用时, 使用useCallback对函数进行处理.
const increment2 = useCallback(() => {
console.log("执行increment2函数");
setCount(count + 1);
}, [count]);
<HYButton title="btn2" increment={increment2}/>
const Button = memo((props) => {
console.log("HYButton重新渲染: " + props.title);
return <button onClick={props.increment}>HYButton +1</button>
});
useRef
返回一个对象,返回的ref对象在组件的整个生命周期保持不变。用法:引入dom元素,保存一个数据,这个对象在整个生命周期中保持不变
useRef引用DOM
import React, { useRef } from 'react';
class TestCpn extends React.Component {
render() {
return <h2>TestCpn</h2>;
}
}
export default function RefHookDemo01() {
const titleRef = useRef();
const inputRef = useRef();
const testRef = useRef();
const changeDOM = () => {
titleRef.current.innerHTML = 'Hello World';
inputRef.current.focus();
console.log(testRef.current);
};
return (
<div>
<h2 ref={titleRef}>RefHookDemo01</h2>
<input ref={inputRef} type='text' />
<TestCpn ref={testRef} />
<button onClick={changeDOM}>修改DOM</button>
</div>
);
}
useImperativeHandle
ref补充:父组件通过ref获取子组件中DOM元素
原理使用forwardRef
import React, { useRef, forwardRef } from 'react';
const Input = forwardRef((props, ref) => {
return <input ref={ref} type="text"/>
})
export default function ForwardRefDemo() {
const inputRef = useRef();
return (
<div>
<HYInput ref={inputRef}/>
<button onClick={e => inputRef.current.focus()}>聚焦</button>
</div>
)
}
useImperativeHandle对forwardRef做限制,限制子组件暴露过多内容给父组件
import React, { useRef, forwardRef, useImperativeHandle } from 'react';
const HYInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
//父组件需要其他操作则暴露对应的函数
focus: () => {
inputRef.current.focus();
}
}), [inputRef])
return <input ref={inputRef} type="text"/>
})
export default function UseImperativeHandleHookDemo() {
const inputRef = useRef();
return (
<div>
<HYInput ref={inputRef}/>
<button onClick={e => inputRef.current.focus()}>聚焦</button>
</div>
)
}
useLayoutEffect
useEffect:渲染DOM更新后执行,不会阻塞DOM的更新
useLayoutEffect:渲染的内容更新到DOM上之前更新,会阻塞DOM更新
自定义hook
本质上:一种函数代码逻辑的抽取。将相同的逻辑抽取到单独的函数中,自定义hook需要使用use开头
自定义hook实现
Context共享数据
app.js
import React, { createContext } from 'react';
//新建context对象并导出
export const UserContext = createContext();
export const TokenContext = createContext();
//给对应的context上添加value
<UserContext.Provider value={{name: "why", age: 18}}>
<TokenContext.Provider value="fdafdafafa">
<CustomContextShareHook/>
</TokenContext.Provider>
</UserContext.Provider>
user-hook.js
//拿到app.js中提供的值,并作为函数返回值导出
import { useContext } from "react";
import { UserContext, TokenContext } from "../App";
function useUserContext() {
const user = useContext(UserContext);
const token = useContext(TokenContext);
return [user, token];
}
export default useUserContext;
context.js
//在需要使用的组建中导入自定义hook,拿到里面提供的值
import React, { useContext } from 'react';
import useUserContext from '../hooks/user-hook';
export default function CustomContextShareHook() {
const [user, token] = useUserContext();
console.log(user, token);
return (
<div>
<h2>CustomContextShareHook</h2>
</div>
)
}
获取当前滚动到的位置
scrill-position-hook.js
import { useState, useEffect } from 'react';
function useScrollPosition() {
const [scrollPosition, setScrollPosition] = useState(0);
useEffect(() => {
const handleScroll = () => {
setScrollPosition(window.scrollY);
}
document.addEventListener("scroll", handleScroll);
return () => {
document.removeEventListener("scroll", handleScroll)
}
}, []);
return scrollPosition;
}
export default useScrollPosition;
scroll.js
import React from 'react';
import useScrollPosition from '../hooks/scroll-position-hook';
export default function CustomScrollPositionHook() {
const position = useScrollPosition();
return (
<div style={{ padding: '1000px 0' }}>
<h2 style={{ position: 'fixed', left: 0, top: 0 }}>CustomScrollPositionHook: {position}</h2>
</div>
);
}
本地存储
一般写法
import React, { memo, useState, useEffect } from 'react';
export default memo(function App() {
const [name, setName] = useState(() => {
const name = JSON.parse(window.localStorage.getItem('name'));
return name;
});
useEffect(() => {
window.localStorage.setItem('name', JSON.stringify(name));
}, [name]);
return (
<div>
<h2>{name}</h2>
<button onClick={e => setName('wingchiehpih')}>设置name</button>
</div>
);
});
自定义hook
import {useState, useEffect} from 'react';
function useLocalStorage(key) {
const [name, setName] = useState(() => {
const name = JSON.parse(window.localStorage.getItem(key));
return name;
});
useEffect(() => {
window.localStorage.setItem(key, JSON.stringify(name));
}, [name]);
return [name, setName];
}
export default useLocalStorage;
localstorage.js
import React from 'react';
import useLocalStorage from '../hooks/local-store-hook';
export default function CustomDataStoreHook() {
const [name, setName] = useLocalStorage('name');
return (
<div>
<h2>CustomDataStoreHook: {name}</h2>
<button onClick={e => setName('kobe')}>设置name</button>
</div>
);
}