React – Hooks 常识系统概览
- 起步:我的Hooks 学习材料
-
开端:
Hooks
发生的原因和发生背景 -
Hooks Api:
- 状况钩子:
useState
,useReducer
- 副作用钩子:
useEffect
,useLayoutEffect
- 同享状况钩子:
useCotext
- 回忆值钩子:
useMemo
- 回忆回调函数钩子:
useCallback
- ref 钩子:
useRef
- ImperativeHandle钩子:
useImperativeHandle
- 状况钩子:
- React Hooks in TypeScript
-
React Hooks 造轮子
- DOM 副作用修正 / 监听
- 组件辅佐
- 动画
- 恳求
- 表单
- 模仿生命周期
- 存数据
- 封装原有库
- React Hooks 源码:待续
起步
我的 Hook 学习材料
Hooks Start
React Hooks Tutorial
useState – useReducer
how to useState in React
useState – add-item-to-list
Remove an Item from a List in React
How to update Item from a List in React
useEffect – useLayoutEffect
How to useEffect in React
How to fetchData in useEffect
A complete Guide to useEffect
using the effect Hook
React useLayoutEffect vs. useEffect with examples
useContext
How to use React Context
How to useContext in React
useMemo
【译】什么时分运用 useMemo 和 useCallback
docs useMemo
useCallback
【译】什么时分运用 useMemo 和 useCallback
docs useCallback
如何錯誤地运用 React hooks useCallback 來保存相同的 function instance
useRef
How to use React Ref
docs useRef
React: Using Refs with the useRef Hook
useImperativeHandle
docs useImperativeHandle
React Hooks in TypeScript
React Hooks in TypeScript
React Hooks 造轮子
react-use
精读《怎样用 React Hooks 造轮子》
开端
Hooks 发生的原因,背景和意图
Hooks 发生的背景和意图
React Hooks
是在 2018 年 10 月
的 React Conf
上引入的,替换原先类组件的书写方法,使得函数式组件支撑 state
和 side-effects
。React-Hooks
之前的函数式组件是无状况组件,所以咱们能够说 React-Hooks
就是增强的函数式组件,支撑状况和副作用的全功能组件。
Hooks 发生的原因
- 函数式组件愈加优雅,愈加轻量级,经过一个小计数器
demo
就能够发现。前者是 Hooks 之前的类组件,后者式函数式组件
// Hooks之前的类组件
import React, { Component } from "react";
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
export default Counter;
// Hooks
import React from "react";
function Counter() {
const [count, setCount] = React.useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default Counter;
-
React
开发人员的学习曲线不会很峻峭,只需求知晓state, side-effects
概念就能够,不需求在像学习类组件的时分,需求知道Class, this 绑定,super,state, setState, render
函数,生命周期。根本消除了带给React
初学者的挫败感。 -
像
React 核心成员
说的那样,Hooks
消除了wrapper hell
– 包装阴间,我就遇到过这种包装阴间场景:其时的状况是一个类组件,包装了redux
供给的connect
函数,包装国际化库供给的Api
函数,包装自己封装的高阶组件函数,还包装了withRouter
函数。这种代码可读性极差,层级嵌套的代码像个怪物。
useState
根本介绍
为函数组件引入 state
,以及更新state
的 setState
函数。
Tips:与
class
组件傍边setState
不同,Hook
的setState
不会自动兼并更新目标,所以咱们会运用打开运算符,结合Hook
的setState
来到达更新目标的意图。
根本运用
- 传入初始状况,回来一个数组,数组的第一个成员是
state
,第二个成员是改动state
的函数。
useState 操练:歌曲的增删改
import { useState } from "react";
const InitialData = [{ name: "歌名1", id: 1, isComplete: false }];
export const HookUseStateCpn = () => {
const [songState, setSongState] = useState(InitialData);
const [songName, setSongName] = useState("");
const setName = (e) => {
setSongName(e.target.value);
};
/**
* 添加歌曲处理
*/
const handleAddSong = () => {
const newData = songState.concat({
name: songName,
id: songState.length + 1,
isComplete: false,
});
setSongState(newData);
setSongName("");
};
/**
* 删去歌曲处理
* @param {*} id 歌曲 id
*/
const handleDeleteSong = (id) => {
const newData = songState.filter((item) => item.id !== id);
setSongState(newData);
};
/**
* 修改歌曲处理
* @param {*} id 歌曲id
*/
const handleEditSong = (id) => {
const newData = songState.map((item, index) => {
if (item.id === id) item.isComplete = !item.isComplete;
return item;
});
setSongState(newData);
};
return (
<>
<List
songState={songState}
handleDeleteSong={handleDeleteSong}
handleEditSong={handleEditSong}
/>
<AddItem
setName={setName}
handleAddSong={handleAddSong}
songName={songName}
/>
</>
);
};
// 子组件 AddItem
const AddItem = ({ setName, handleAddSong, songName }) => {
return (
<>
<input onChange={(e) => setName(e)} value={songName}></input>
<button onClick={(e) => handleAddSong(e)}>添加</button>
</>
);
};
// 子组件 List
const List = ({ songState, handleDeleteSong, handleEditSong }) => {
return (
<>
<h1>最喜欢的歌</h1>
{songState.map((song) => {
return (
<div key={song.id}>
<h4
style={{
textDecoration: song.isComplete ? "line-through" : "none",
}}
>
{song.name} -- {song.id}{" "}
<button onClick={() => handleEditSong(song.id)}>
{song.isComplete ? "吊销完结" : "完结"}
</button>
<button onClick={() => handleDeleteSong(song.id)}>删去</button>
</h4>
</div>
);
})}
</>
);
};
useReducer
根本介绍
咱们说,useState
为 React-Hooks
引入了状况办理,useReducer
的作用也是为 React-Hooks
供给了状况办理。useReducer
是 useState
的增强和替代 API,useReducer
用于杂乱状况办理和功能优化, 功能优化是因为它能够凭借 Context API
传递 dispatch
函数给子组件,因为dispatch
函数始终不变,所以子组件不会从头烘托。
Tips:关于运用或者把握了
Redux
的开发者来讲,useReducer
运用起来会很顺手。因为其间像:store
,reducer
,state
,dispatch
,action
的概念现已把握和了解了。知道store
的更新流程是怎样样的,运用useReducer
自然会愈加顺手一些
根本运用
import { useReducer, useState } from "react";
const InitialData = [{ name: "歌名1", id: 1, isComplete: false }];
const SongReducer = (state, action) => {
const id = action?.payload?.id;
switch (action.type) {
case "ADD_SONG":
return [...state, { name: action.payload.name, id, isComplete: false }];
case "DELETE_SONG":
return state.filter((item) => {
return item.id !== id;
});
case "EDIT_SONG":
return state.map((item) => {
if (item.id === id) {
const updateItem = {
...item,
isComplete: !action.payload.isComplete,
};
return updateItem;
}
return item;
});
default:
return new Error();
}
};
export const HookUseReducerCpn = () => {
const [songReducerState, dispatchSongData] = useReducer(
SongReducer,
InitialData
);
const [songName, setSongName] = useState("");
const setName = (e) => {
setSongName(e.target.value);
};
/**
* 添加歌曲处理
*/
const handleAddSong = () => {
dispatchSongData({
type: "ADD_SONG",
payload: { name: songName, id: songReducerState.length + 1 },
});
setSongName("");
};
/**
* 删去歌曲处理
* @param {*} id 歌曲 id
*/
const handleDeleteSong = (id) => {
dispatchSongData({ type: "DELETE_SONG", payload: { id } });
};
/**
* 修改歌曲处理
* @param {*} id 歌曲id
*/
const handleEditSong = (id, isComplete) => {
dispatchSongData({ type: "EDIT_SONG", payload: { id, isComplete } });
};
return (
<>
<List
songState={songReducerState}
handleDeleteSong={handleDeleteSong}
handleEditSong={handleEditSong}
/>
<AddItem
setName={setName}
handleAddSong={handleAddSong}
songName={songName}
/>
</>
);
};
const AddItem = ({ setName, handleAddSong, songName }) => {
return (
<>
<input onChange={(e) => setName(e)} value={songName}></input>
<button onClick={(e) => handleAddSong(e)}>添加</button>
</>
);
};
const List = ({ songState, handleDeleteSong, handleEditSong }) => {
return (
<>
<h1>最喜欢的歌</h1>
{songState.map((song) => {
return (
<div key={song.id}>
<h4
style={{
textDecoration: song.isComplete ? "line-through" : "none",
}}
>
{song.name} -- {song.id}{" "}
<button onClick={() => handleEditSong(song.id, song.isComplete)}>
{song.isComplete ? "吊销完结" : "完结"}
</button>
<button onClick={() => handleDeleteSong(song.id)}>删去</button>
</h4>
</div>
);
})}
</>
);
};
useEffect
根本介绍
-
useEffect
为React
引入了副作用函数,你能够运用useEffect
各种用法来决定,副作用函数在组件挂载时,组件烘托时仍是在组件更新烘托时运转,你能够在副作用函数傍边,进行mutations
(状况改动),subscriptions
(添加订阅),timers
(设置定时器),logging
(添加日志),fetch data
(网络恳求)。
每一次组件的烘托都有对应这一次烘托 state
和 props
在开端聊 useEffect
之前,咱们先来聊一聊组件的烘托,咱们首先要明确,每一次组件的烘托都有对应这一次 state
和 props
,经过下面的定时器事例,你会对此有更深的了解。
// 这是一个简略的计数器事例
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
当咱们点击按钮改动组件状况的时分,React
会从头烘托组件,每一次烘托都会从头创立组件函数,拿到归于本次的 count
状况,所以这个 count
值只是函数作用域傍边的一个常量。经过下面的代码,你会对此有愈加深化的了解。
// During first render
function Counter() {
const count = 0; // Returned by useState()
// ...
<p>You clicked {count} times</p>;
// ...
}
// After a click, our function is called again
function Counter() {
const count = 1; // Returned by useState()
// ...
<p>You clicked {count} times</p>;
// ...
}
// After another click, our function is called again
function Counter() {
const count = 2; // Returned by useState()
// ...
<p>You clicked {count} times</p>;
// ...
}
所以计数器事例中的 count
没有什么魔法(magic),只是一个常量,也没有像 vue
傍边的 watcher
,proxy
,或者是 databinding
。
每一次组件烘托,都有对应这一次烘托的作业处理函数
咱们明确了每一次组件的烘托都有对应这一次 state
和 props
之后,第二个要明确的是每一次组件烘托,都有对应这一次的作业处理函数。经过下面的事例,你会对此有更深的了解。
function Counter() {
const [count, setCount] = useState(0);
function handleAlertClick() {
setTimeout(() => {
alert("You clicked on: " + count);
}, 3000);
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
<button onClick={handleAlertClick}>Show alert</button>
</div>
);
}
- 点击添加
counter
到 3 - 点击一下
“Show alert”
- 点击添加
counter
到 5 并且在定时器回调触发前完结
最终的成果竟然是 3 而不是 5,下面的代码会让你有愈加深入的了解。
// During first render
function Counter() {
const count = 0; // Returned by useState()
// ...
function handleAlertClick() {
setTimeout(() => {
alert("You clicked on: " + count);
}, 3000);
}
// ...
}
// After a click, our function is called again
function Counter() {
const count = 1; // Returned by useState()
// ...
function handleAlertClick() {
setTimeout(() => {
alert("You clicked on: " + count);
}, 3000);
}
// ...
}
// After another click, our function is called again
function Counter() {
const count = 2; // Returned by useState()
// ...
function handleAlertClick() {
setTimeout(() => {
alert("You clicked on: " + count);
}, 3000);
}
// ...
}
所以实际上,每一次烘托都有一个“新版别”
的 handleAlertClick(作业处理函数)
。每一个版别的 handleAlertClick(作业处理函数)
“记住” 了它自己本次对应的 count
:
每一次组件烘托都有对应这一次烘托的 Effects
这是一个简略的 useEffect
事例
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
每一次组件的烘托,都有对应这一次烘托的 Effects
,下面的代码会让你有愈加深入的了解
// During first render
function Counter() {
// ...
useEffect(
// Effect function from first render
() => {
document.title = `You clicked ${0} times`;
}
);
// ...
}
// After a click, our function is called again
function Counter() {
// ...
useEffect(
// Effect function from second render
() => {
document.title = `You clicked ${1} times`;
}
);
// ...
}
// After another click, our function is called again
function Counter() {
// ...
useEffect(
// Effect function from third render
() => {
document.title = `You clicked ${2} times`;
}
);
// ..
}
React
会记住你供给的 effects
函数,等候每次 React
将 DOM
更新烘托制作到 screen
上之后去调用 effects
函数,本质上,effects
函数每次“看到的”都是对应那一次烘托的 state
和 props
.
useEffect 回来的整理函数是怎样回事
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.id, handleStatusChange);
};
});
假设第一次烘托的时分 props 是{id: 10},第二次烘托的时分是{id: 20}
- 会发生下面的作业:
- React 烘托{id: 20}的 UI。
- 浏览器制作。咱们在屏幕上看到{id: 20}的 UI。
- React 铲除{id: 10}的 effect。
- React 运转{id: 20}的 effect。
向 useEffects 中传递依靠
上方的 useEffect 事例,只要一个函数作为参数,这样的成果是在组件每次烘托时都会调用 effects 函数,这样并不高效,还经常导致无限烘托问题,所以咱们需求传递第二参数来处理问题,第二参数是一个数组依靠项,只要当依靠项发生改动时, 才会调用 effects 函数.
useEffect(() => {
document.title = "Hello, " + name;
}, [name]); // Our deps
只要当 name 发生改动时才会调用 effects 函数.
削减 useEffects 傍边的依靠性来到达功能优化
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(id);
}, [count]);
现在依靠数组正确了。尽管它可能不是太理想但的确处理了上面的问题。现在,每次 count 修正都会从头运转 effect,并且定时器中的 setCount(count + 1)会正确引证某次烘托中的 count 值:
// First render, state is 0
function Counter() {
// ...
useEffect(
// Effect from first render
() => {
const id = setInterval(() => {
setCount(0 + 1); // setCount(count + 1)
}, 1000);
return () => clearInterval(id);
},
[0] // [count]
);
// ...
}
// Second render, state is 1
function Counter() {
// ...
useEffect(
// Effect from second render
() => {
const id = setInterval(() => {
setCount(1 + 1); // setCount(count + 1)
}, 1000);
return () => clearInterval(id);
},
[1] // [count]
);
// ...
}
这能处理问题可是咱们的定时器会在每一次 count 改动后铲除和从头设定。这应该不是咱们想要的成果.所以咱们需求削减依靠项的一起,满意咱们的需求.
削减依靠计划一:seState 的 updater function
useEffect(() => {
const id = setInterval(() => {
setCount((c) => c + 1);
}, 1000);
return () => clearInterval(id);
}, []);
削减依靠计划二:useReducer 的 dispatch function
function Counter({ step }) {
const [count, dispatch] = useReducer(reducer, 0);
function reducer(state, action) {
if (action.type === "tick") {
return state + step;
} else {
throw new Error();
}
}
useEffect(() => {
const id = setInterval(() => {
dispatch({ type: "tick" });
}, 1000);
return () => clearInterval(id);
}, [dispatch]);
return <h1>{count}</h1>;
}
削减依靠计划三:将函数界说到 effects 函数傍边
function SearchResults() {
// ...
useEffect(() => {
// We moved these functions inside!
function getFetchUrl() {
return "https://hn.algolia.com/api/v1/search?query=react";
}
async function fetchData() {
const result = await axios(getFetchUrl());
setData(result.data);
}
fetchData();
}, []); // ✅ Deps are OK
// ...
}
削减依靠计划四:将函数界说到 effects 函数外面
针关于第三种处理计划,缺点是不能复用 getFetchUrl 函数,假如多个地方一起运用到了该函数,只能每次都从头界说.
function SearchResults() {
// Re-triggers all effects on every render
function getFetchUrl(query) {
return "https://hn.algolia.com/api/v1/search?query=" + query;
}
useEffect(() => {
const url = getFetchUrl("react");
// ... Fetch data and do something ...
}, [getFetchUrl]); // Deps are correct but they change too often
useEffect(() => {
const url = getFetchUrl("redux");
// ... Fetch data and do something ...
}, [getFetchUrl]); // Deps are correct but they change too often
// ...
}
两个更简略的处理办法:
- 假如该函数没有依靠任何组件状况,放到组件的外面傍边去.
// ✅ Not affected by the data flow
function getFetchUrl(query) {
return "https://hn.algolia.com/api/v1/search?query=" + query;
}
function SearchResults() {
useEffect(() => {
const url = getFetchUrl("react");
// ... Fetch data and do something ...
}, []); // ✅ Deps are OK
useEffect(() => {
const url = getFetchUrl("redux");
// ... Fetch data and do something ...
}, []); // ✅ Deps are OK
// ...
}
- 假如该函数依靠了组件状况,那就放在组件傍边,运用 callback 进行包裹.
function SearchResults() {
// ✅ Preserves identity when its own deps are the same
const getFetchUrl = useCallback((query) => {
return "https://hn.algolia.com/api/v1/search?query=" + query;
}, []); // ✅ Callback deps are OK
useEffect(() => {
const url = getFetchUrl("react");
// ... Fetch data and do something ...
}, [getFetchUrl]); // ✅ Effect deps are OK
useEffect(() => {
const url = getFetchUrl("redux");
// ... Fetch data and do something ...
}, [getFetchUrl]); // ✅ Effect deps are OK
// ...
}
根本运用
向服务器恳求数据(fetch data) – useState
const useDataApi = (initialUrl, initialData) => {
const [data, setData] = useState(initialData);
const [url, setUrl] = useState(initialUrl);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsError(false);
setIsLoading(true);
try {
const result = await axios(url);
setData(result.data);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
};
fetchData();
}, [url]);
return [{ data, isLoading, isError }, setUrl];
};
向服务器恳求数据(fetch data) – useReducer
const dataFetchReducer = (state, action) => {
switch (action.type) {
case "FETCH_INIT":
return {
...state,
isLoading: true,
isError: false,
};
case "FETCH_SUCCESS":
return {
...state,
isLoading: false,
isError: false,
data: action.payload,
};
case "FETCH_FAILURE":
return {
...state,
isLoading: false,
isError: true,
};
default:
throw new Error();
}
};
const useDataApi = (initialUrl, initialData) => {
const [url, setUrl] = useState(initialUrl);
const [state, dispatch] = useReducer(dataFetchReducer, {
isLoading: false,
isError: false,
data: initialData,
});
useEffect(() => {
let didCancel = false;
const fetchData = async () => {
dispatch({ type: "FETCH_INIT" });
try {
const result = await axios(url);
if (!didCancel) {
dispatch({ type: "FETCH_SUCCESS", payload: result.data });
}
} catch (error) {
if (!didCancel) {
dispatch({ type: "FETCH_FAILURE" });
}
}
};
fetchData();
return () => {
didCancel = true;
};
}, [url]);
return [state, setUrl];
};
useEffects: 第一次烘托(挂载)后和每一次的组件更新都会履行
— meaning it runs on the first render of the component (also called on mount or mounting of the component) and on every re-render of the component (also called on update or updating of the component).
const Toggler = ({ toggle, onToggle }) => {
React.useEffect(() => {
console.log("I run on every render: mount + update.");
});
return (
<div>
<button type="button" onClick={onToggle}>
Toggle
</button>
{toggle && <div>Hello React</div>}
</div>
);
};
useEffect:只要在第一次烘托(挂载)后履行
If the dependency array is empty, the side-effect function used in React’s useEffect Hook has no dependencies, meaning it runs only the first time a component renders(mount).
const Toggler = ({ toggle, onToggle }) => {
React.useEffect(() => {
console.log("I run only on the first render: mount.");
}, []);
return (
<div>
<button type="button" onClick={onToggle}>
Toggle
</button>
{toggle && <div>Hello React</div>}
</div>
);
};
useEffect: 组件更新的时分履行,包含第一次烘托(挂载)
Now the side-effect function for this React component runs only when the variable in the dependency array changes. However, note that the function runs also on the component’s first render (mount).
const Toggler = ({ toggle, onToggle }) => {
const [title, setTitle] = React.useState("Hello React");
React.useEffect(() => {
console.log("I run if toggle or title change (and on mount).");
}, [toggle, title]);
const handleChange = (event) => {
setTitle(event.target.value);
};
return (
<div>
<input type="text" value={title} onChange={handleChange} />
<button type="button" onClick={onToggle}>
Toggle
</button>
{toggle && <div>{title}</div>}
</div>
);
};
useEffect: 只在组件更新的时分履行,第一次烘托(挂载)不履行
const Toggler = ({ toggle, onToggle }) => {
const didMount = React.useRef(false);
React.useEffect(() => {
if (didMount.current) {
console.log("I run only if toggle changes.");
} else {
didMount.current = true;
}
}, [toggle]);
return (
<div>
<button type="button" onClick={onToggle}>
Toggle
</button>
{toggle && <div>Hello React</div>}
</div>
);
};
useEffect: 只在组件更新的时分履行一次,第一次烘托(挂载)不履行
const Toggler = ({ toggle, onToggle }) => {
const calledOnce = React.useRef(false);
React.useEffect(() => {
if (calledOnce.current) {
return;
}
if (toggle === false) {
console.log("I run only once if toggle is false.");
calledOnce.current = true;
}
}, [toggle]);
return (
<div>
<button type="button" onClick={onToggle}>
Toggle
</button>
{toggle && <div>Hello React</div>}
</div>
);
};
useEffect: 整理回调函数
import * as React from "react";
const App = () => {
const [timer, setTimer] = React.useState(0);
React.useEffect(() => {
const interval = setInterval(() => setTimer(timer + 1), 1000);
return () => clearInterval(interval);
}, [timer]);
return <div>{timer}</div>;
};
export default App;
useLayoutEffect
根本介绍
useLayoutEffect
和 useEffect
有着相同的函数签名,两者在大多数状况下能够彼此替换,useLayoutEffect
和 useEffect
的本质区别是触发时机的不一致,useEffect
发生在将 DOM 烘托制作到屏幕之后,而 useLayoutEffect
发生在 DOM 烘托制作到屏幕之前同步触发.也就是useLayoutEffect
比useEffect
更快履行.useLayouEffect
用在杂乱的动画傍边,作用比 useEffect
愈加洁净(没有闪烁(flicker)的状况).当然大多数状况下,仍是运用 useEffect
,因为 useEffect
先将 DOM 改动(mutation)
进行制作,后履行effects
函数,而 useLayoutEffect
是先核算 effects
函数再制作,核算的进程会在一定程度上阻塞浏览器的烘托.
The useLayoutEffect function is triggered synchronously before the DOM mutations are painted. However, the useEffect function is called after the DOM mutations are painted.
useContext
根本介绍
useContext
处理垂直方向上嵌套层级太深的组件传递 props
太冗长的问题,该问题也被叫做 props drilling
问题。
+----------------+
| |
| A |
| |Props |
| v |
| |
+--------+-------+
|
+---------+-----------+
| |
| |
+--------+-------+ +--------+-------+
| | | |
| | | + |
| B | | |Props |
| | | v |
| | | |
+----------------+ +--------+-------+
|
+--------+-------+
| |
| + |
| |Props |
| v |
| |
+--------+-------+
|
+--------+-------+
| |
| + |
| |Props |
| C |
| |
+----------------+
根本运用
import * as React from "react";
import { createContext, useState, useContext } from "react";
import ReactDOM from "react-dom";
const genColor = () => `hsla(${Math.random() * 360}, 70%, 50%)`;
type MyContext = { color: string, changer: (any) => void };
const ThemeContext = createContext < MyContext > null;
const Comp = () => {
const { color, changer } = useContext(ThemeContext);
return (
<button style={{ color, fontSize: "2em" }} onClick={changer}>
Hello World! Click to change color
</button>
);
};
const App = () => {
const [state, setState] = useState({ color: "green" });
const { color } = state;
const changer = () => setState({ color: genColor() });
return (
<ThemeContext.Provider value={{ color, changer }}>
<Comp />
</ThemeContext.Provider>
);
};
useContext 最佳实践
封装useContext
独自成为一个context
文件,这里是currencyContext.js
。将context.Provider
,context数据
,改动context目标的回调函数
封装到一个文件,导出一个封装Provider
在顶层的高阶组件(这里是CurrencyProvider
)和一个归于该context目标
的自界说Hook(这里是useCurrency
)。
currencyContext.js
const CURRENCIES = {
Euro: {
code: "EUR",
label: "Euro",
conversionRate: 1, // base conversion rate
},
Usd: {
code: "USD",
label: "US Dollar",
conversionRate: 1.19,
},
};
const useCurrency = () => {
const [currency, setCurrency] = React.useContext(CurrencyContext);
const handleCurrency = (value) => {
setCurrency(value);
};
return { value: currency, onChange: handleCurrency };
};
const CurrencyProvider = ({ children }) => {
const [currency, setCurrency] = React.useState(CURRENCIES.Euro);
return (
<CurrencyContext.Provider value={[currency, setCurrency]}>
{children}
</CurrencyContext.Provider>
);
};
export { CurrencyProvider, useCurrency, CURRENCIES };
App.js
import { CurrencyProvider, useCurrency, CURRENCIES } from "./currency-context";
const App = () => {
return (
<CurrencyProvider>
<CurrencyButtons />
</CurrencyProvider>
);
};
const CurrencyButtons = () => {
const { onChange } = useCurrency();
return Object.values(CURRENCIES).map((item) => (
<CurrencyButton key={item.label} onClick={() => onChange(item)}>
{item.label}
</CurrencyButton>
));
};
useCallback
根本介绍
useCallback
回来一个 memoried
回调函数,只要在依靠项改动的时分,才会更新这个回调函数。useCallback
能够处理引证持平问题,防止子组件进行屡次不必要的从头烘托。
根本运用
处理引证持平问题 1: useCallback + updater function(用函数来更新状况)
import React, { useState, useCallback, useRef } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const Button = React.memo(({ handleClick }) => {
const refCount = useRef(0);
return (
<button
onClick={handleClick}
>{`button render count ${refCount.current++}`}</button>
);
});
function App() {
const [isOn, setIsOn] = useState(false);
const handleClick = useCallback(() => setIsOn((prevIsOn) => !prevIsOn), []);
return (
<div className="App">
<h1>{isOn ? "On" : "Off"}</h1>
<Button handleClick={handleClick} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
处理引证持平问题 2:useCallback + useReducer(dispatch 函数作为依靠)
const Button = React.memo(({ handleClick, text }) => {
const refCount = useRef(0);
return (
<button onClick={handleClick}>
{`${text}`}
<span className={"renderCount"}>
self render count {refCount.current++}
</span>
</button>
);
});
const reducer = (state, action) => {
switch (action.type) {
case "INCREASE_A":
return {
...state,
numA: state.numA + 1,
};
case "DECREASE_A":
return {
...state,
numA: state.numA - 1,
};
case "INCREASE_B":
return {
...state,
numB: state.numB + 1,
};
case "DECREASE_B":
return {
...state,
numB: state.numB - 1,
};
case "A_PLUS_B":
return {
...state,
result: state.numA + state.numB,
};
case "A_MINUS_B":
return {
...state,
result: state.numA - state.numB,
};
default:
return state;
}
};
function App() {
const [{ numA, numB, result }, dispatch] = useReducer(reducer, {
numA: 0,
numB: 0,
result: null,
});
const handlePlusAClick = useCallback(
() => dispatch({ type: "INCREASE_A" }),
[dispatch]
);
const handleMinusAClick = useCallback(
() => dispatch({ type: "DECREASE_A" }),
[dispatch]
);
const handlePlusBClick = useCallback(
() => dispatch({ type: "INCREASE_B" }),
[dispatch]
);
const handleMinusBClick = useCallback(
() => dispatch({ type: "DECREASE_B" }),
[dispatch]
);
const handleAPlusB = useCallback(
() => dispatch({ type: "A_PLUS_B" }),
[dispatch]
);
const handleAMinusB = useCallback(
() => dispatch({ type: "A_MINUS_B" }),
[dispatch]
);
return (
<div className="App">
<div className={"num"}>NumA: {numA}</div>
<Button text={"+"} handleClick={handlePlusAClick} />
<Button text={"-"} handleClick={handleMinusAClick} />
<div className={"num"}>NumB: {numB}</div>
<Button text={"+"} handleClick={handlePlusBClick} />
<Button text={"-"} handleClick={handleMinusBClick} />
<div className={"num"}>Result: {result}</div>
<Button text={"A + B"} handleClick={handleAPlusB} />
<Button text={"A - B"} handleClick={handleAMinusB} />
</div>
);
}
处理引证持平问题 3:useCallback + useEffect(向 useEffect 传递引证持平的函数依靠)
function Foo({ bar, baz }) {
React.useEffect(() => {
const options = { bar, baz };
buzz(options);
}, [bar, baz]);
return <div>foobar</div>;
}
function Blub() {
const bar = React.useCallback(() => {}, []);
const baz = React.useMemo(() => [1, 2, 3], []);
return <Foo bar={bar} baz={baz} />;
}
useCallback 误区运用:给所有函数包装上 useCallback
import { useState, useCallback } from "react";
export const HookUsecallback = () => {
const [count, setCount] = useState(0);
const handleAddFn = useCallback(() => {
console.log("handleAdd");
setCount(count + 1);
}, [count]);
return (
<>
<div>{count}</div>
<button onClick={() => handleAddFn()}>+1</button>
</>
);
};
上面的实例,就算不包裹 useCallback
也能够到达相同的作用,但useCallback
做了更多的作业,调用 React.useCallback
,界说了一个数组 []
,反而使得功能和内存变得糟糕。
useCallback 误区运用:运用 useCallback 来优化核算开支
- 尽管能够在
useCallback
中的memoried
函数傍边书写核算逻辑,每次调用也都会取得核算成果,可是useCallback
回来的是一个函数,所以每次运用的时分,都会调用memoried
函数从头核算。所以不能像useMemo
相同回来一个引证持平的值来优化核算开支。
useMemo
根本介绍
useMemo
回来一个 memoried
值,只要在依靠项改动的时分,才会从头核算memoried
值。useMemo
从两个方面进行功能优化, 既能够处理引证持平问题,防止子组件进行屡次不必要的从头烘托又能够优化核算开支,防止屡次不必要的从头核算。
根本运用
处理优化核算开支问题:useMemo
import "./styles.css";
import { useMemo, useState } from "react";
export default function App() {
const [count, setCount] = useState(3);
const [name, setName] = useState("Ryan");
function computedExpensiveValue(count) {
console.log("computed expensive value");
let sum = 0;
while (count > 0) {
sum += count;
count--;
}
return sum;
}
const result = useMemo(() => computedExpensiveValue(count), [count]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<input
value={count}
onChange={(e) => setCount(Number(e.target.value))}
></input>
<input value={name} onChange={(e) => setName(e.target.value)}></input>
<h2>{result}</h2>
<h3>{name}</h3>
</div>
);
}
处理引证持平问题 1: useMemo + updater function(用函数来更新状况)
const Button = React.memo(({ handleClick }) => {
const refCount = useRef(0);
return (
<button
onClick={handleClick}
>{`button render count ${refCount.current++}`}</button>
);
});
function App() {
const [isOn, setIsOn] = useState(false);
const handleClick = useMemo(() => () => setIsOn((prevIsOn) => !prevIsOn), []);
return (
<div className="App">
<h1>{isOn ? "On" : "Off"}</h1>
<Button handleClick={handleClick} />
</div>
);
}
处理引证持平问题 2:useMemo + useEffect(向 useEffect 传递引证持平的函数依靠)
function Foo({ bar, baz }) {
React.useEffect(() => {
const options = { bar, baz };
buzz(options);
}, [bar, baz]);
return <div>foobar</div>;
}
function Blub() {
const bar = React.useCallback(() => {}, []);
const baz = React.useMemo(() => [1, 2, 3], []);
return <Foo bar={bar} baz={baz} />;
}
处理引证持平问题 3:useMemo + useReducer(dispatch 函数作为依靠)
import React, { useReducer, useMemo, useRef } from "react";
import "./App.css";
const Button = React.memo(({ handleClick, text }) => {
const refCount = useRef(0);
return (
<button onClick={handleClick}>
{`${text}`}
<span className={"renderCount"}>
self render count {refCount.current++}
</span>
</button>
);
});
const reducer = (state, action) => {
switch (action.type) {
case "INCREASE_A":
return {
...state,
numA: state.numA + 1,
};
case "DECREASE_A":
return {
...state,
numA: state.numA - 1,
};
case "INCREASE_B":
return {
...state,
numB: state.numB + 1,
};
case "DECREASE_B":
return {
...state,
numB: state.numB - 1,
};
case "A_PLUS_B":
return {
...state,
result: state.numA + state.numB,
};
case "A_MINUS_B":
return {
...state,
result: state.numA - state.numB,
};
default:
return state;
}
};
function App() {
const [{ numA, numB, result }, dispatch] = useReducer(reducer, {
numA: 0,
numB: 0,
result: null,
});
const handlePlusAClick = useMemo(
() => () => dispatch({ type: "INCREASE_A" }),
[dispatch]
);
const handleMinusAClick = useMemo(
() => () => dispatch({ type: "DECREASE_A" }),
[dispatch]
);
const handlePlusBClick = useMemo(
() => () => dispatch({ type: "INCREASE_B" }),
[dispatch]
);
const handleMinusBClick = useMemo(
() => () => dispatch({ type: "DECREASE_B" }),
[dispatch]
);
const handleAPlusB = useMemo(
() => () => dispatch({ type: "A_PLUS_B" }),
[dispatch]
);
const handleAMinusB = useMemo(
() => () => dispatch({ type: "A_MINUS_B" }),
[dispatch]
);
return (
<div className="App">
<div className={"num"}>NumA: {numA}</div>
<Button text={"+"} handleClick={handlePlusAClick} />
<Button text={"-"} handleClick={handleMinusAClick} />
<div className={"num"}>NumB: {numB}</div>
<Button text={"+"} handleClick={handlePlusBClick} />
<Button text={"-"} handleClick={handleMinusBClick} />
<div className={"num"}>Result: {result}</div>
<Button text={"A + B"} handleClick={handleAPlusB} />
<Button text={"A - B"} handleClick={handleAMinusB} />
</div>
);
}
export default App;
结合 useCallback.md
,会发现 useCallback
能做的,useMemo
也能做,因为 useCallback
只能回来 memoried
函数,而 useMemo
既能回来 memoried
函数,也能回来 memoried
值。
useRef
根本介绍
useRef 主要用在三个方面,第一个方面是作为一个可变的实例值,该实例值的改动不会引起组件的从头烘托。一般用来盯梢不应该触发组件从头烘托的组件状况.第二个方面是 经过 ref 值引证 DOM,来改动 DOM 的状况(表单 focus blur ,按钮 disabled, HTML 元素 class)。第三个方面是经过 ref 回调函数来引证 DOM,将 DOM 节点作为参数传入.每次烘托都会调用该回调函数.
根本运用
useRef 作为可变的实例值:
事例 1: 判断组件是初次烘托仍是从头烘托
function ComponentWithRefInstanceVariable() {
const [count, setCount] = React.useState(0);
function onClick() {
setCount(count + 1);
}
const isFirstRender = React.useRef(true);
React.useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
} else {
console.log(
`
I am a useEffect hook's logic
which runs for a component's
re-render.
`
);
}
});
return (
<div>
<p>{count}</p>
<button type="button" onClick={onClick}>
Increase
</button>
</div>
);
}
useRef ref 值作为 DOM 引证
事例 1:运用 ref focus input
function App() {
return <ComponentWithDomApi label="Label" value="Value" isFocus />;
}
function ComponentWithDomApi({ label, value, isFocus }) {
const ref = React.useRef(); // (1)
React.useEffect(() => {
if (isFocus) {
ref.current.focus(); // (3)
}
}, [isFocus]);
return (
<label>
{/* (2) */}
{label}: <input type="text" value={value} ref={ref} />
</label>
);
}
事例 2: 运用 ref 改动文档标题
function ComponentWithRefRead() {
const [text, setText] = React.useState("Some text ...");
function handleOnChange(event) {
setText(event.target.value);
}
const ref = React.useRef();
React.useEffect(() => {
const { width } = ref.current.getBoundingClientRect();
document.title = `Width:${width}`;
}, [text]);
return (
<div>
<input type="text" value={text} onChange={handleOnChange} />
<div>
<span ref={ref}>{text}</span>
</div>
</div>
);
}
useRef ref 回调函数作为 DOM 引证
每次组件烘托的时分,都会调用 ref 回调函数,传入 DOM 节点作为参数. ref 回调函数对比 ref 值,摆脱了 useEffect 和 useRef 的运用.
事例 1: ref 回调函数作为 DOM 引证
function ComponentWithRefRead() {
const [text, setText] = React.useState("Some text ...");
function handleOnChange(event) {
setText(event.target.value);
}
const ref = (node) => {
if (!node) return;
const { width } = node.getBoundingClientRect();
document.title = `Width:${width}`;
};
return (
<div>
<input type="text" value={text} onChange={handleOnChange} />
<div>
<span ref={ref}>{text}</span>
</div>
</div>
);
}
事例 2: useCallback 增强 ref 回调函数作为 DOM 引证
运用 useCallback (依靠为空数组) 能够包裹 ref 函数,只使得 ref 函数在第一次挂载后履行,之后组件更新,不会履行 ref 函数.来增强 ref 函数.
function ComponentWithRefRead() {
const [text, setText] = React.useState("Some text ...");
function handleOnChange(event) {
setText(event.target.value);
}
const ref = React.useCallback((node) => {
if (!node) return;
const { width } = node.getBoundingClientRect();
document.title = `Width:${width}`;
}, []); // 加上依靠和不包裹useCallback是相同作用
return (
<div>
<input type="text" value={text} onChange={handleOnChange} />
<div>
<span ref={ref}>{text}</span>
</div>
</div>
);
}
useImperativeHandle
根本介绍
useImperativeHandle 能够让你在运用 ref 时自界说露出给父组件的实例值。
根本运用
import "./styles.css";
import { forwardRef, useImperativeHandle, useRef, useState } from "react";
const InputRef = forwardRef((props, ref) => {
const inputEl = useRef(null);
// useImperativeHandle 能够削减向父组件露出DOM节点的属性,只露出第二参数的目标,作为dom.current
useImperativeHandle(ref, () => ({
color: "green",
value: inputEl.current.value,
}));
return <input ref={inputEl}></input>;
});
export default function App() {
const inputRef = useRef(null);
const [isShow, setShow] = useState(true);
return (
<div className="App">
<button onClick={() => console.log(inputRef.current)}>获取</button>
<h1>Hello CodeSandbox</h1>
<button onClick={() => setShow(!isShow)}>切换</button>
<InputRef ref={inputRef} />
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
React Hooks in TypeScript
useState
假如 useState
传入的初始值是简略值,类型会被自动揣度,假如是 null
或者是 undefined
,或者是目标,数组等,能够运用传入泛型.
// inferred as number
const [value, setValue] = useState(0);
// explicitly setting the types
const [value, setValue] = (useState < number) | (undefined > undefined);
const [value, setValue] = useState < Array < number >> [];
interface MyObject {
foo: string;
bar?: number;
}
const [value, setValue] = useState < MyObject > { foo: "hello" };
useContext
useContext
能够根据传入的 Context
目标的类型进行揣度,不需求显现注册类型.
type Theme = "light" | "dark";
const ThemeContext = createContext < Theme > "dark";
const App = () => (
<ThemeContext.Provider value="dark">
<MyComponent />
</ThemeContext.Provider>
);
const MyComponent = () => {
const theme = useContext(ThemeContext);
return <div>The theme is {theme}</div>;
};
useEffect / useLayoutEffect
因为 useEffect / useLayoutEffect
函数没有露出处理回来值的接口,所以不需求类型
useEffect(() => {
const subscriber = subscribe(options);
return () => {
unsubscribe(subscriber)
};
}, [options]);
useMemo / useCallback
useMemo
和 useCallback
都能够根据回来值来揣度类型.
const value = 10;
// inferred as number
const result = useMemo(() => value * 2, [value]);
const multiplier = 2;
// inferred as (value: number) => number
const multiply = useCallback(
(value: number) => value * multiplier,
[multiplier]
);
useRef
null
作为初始值,泛型作为 ref
类型.
const MyInput = () => {
const inputRef = useRef < HTMLInputElement > null;
return <input ref={inputRef} />;
};
当 ref
用来保存可变的实例值时类型,ref.current
的类型 会被自动揣度。
const myNumberRef = useRef(0);
myNumberRef.current += 1;
useReducer
useReducer
会从 reducer
函数的参数中揣度出 要派发(dispatch)
的 action
类型,以及 store
中 state
的类型。
interface State {
value: number;
}
type Action =
| { type: "increment" }
| { type: "decrement" }
| { type: "incrementAmount", amount: number };
const counterReducer = (state: State, action: Action) => {
switch (action.type) {
case "increment":
return { value: state.value + 1 };
case "decrement":
return { value: state.value - 1 };
case "incrementAmount":
return { value: state.value + action.amount };
default:
throw new Error();
}
};
const [state, dispatch] = useReducer(counterReducer, { value: 0 });
dispatch({ type: "increment" });
dispatch({ type: "decrement" });
dispatch({ type: "incrementAmount", amount: 10 });
// TypeScript compilation error
dispatch({ type: "invalidActionType" });
useImperativeHandle
MyInputHandles
为露出 ref
目标的类型,MyInputProps
是被转发组件 props
类型。
export interface MyInputHandles {
focus(): void;
}
const MyInput: RefForwardingComponent<MyInputHandles, MyInputProps> = (
props,
ref
) => {
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
focus: () => {
if (inputRef.current) {
inputRef.current.focus();
}
},
}));
return <input {...props} ref={inputRef} />;
};
export default forwardRef(MyInput);
import MyInput, { MyInputHandles } from "./MyInput";
const Autofocus = () => {
const myInputRef = useRef<MyInputHandles>(null);
useEffect(() => {
if (myInputRef.current) {
myInputRef.current.focus();
}
});
return <MyInput ref={myInputRef} />;
};