## 1. React Hooks
* Hook 是 React 16.8 的新增特性。它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性
* 如果你在編寫函數組件并意識到需要向其添加一些 state,以前的做法是必須將其它轉化為 class。現在你可以在現有的函數組件中使用 Hook
## 2. 解決的問題
* 在組件之間復用狀態邏輯很難,可能要用到render props和高階組件,React 需要為共享狀態邏輯提供更好的原生途徑,Hook 使你在無需修改組件結構的情況下復用狀態邏輯
* 復雜組件變得難以理解,Hook 將組件中相互關聯的部分拆分成更小的函數(比如設置訂閱或請求數據)
* 難以理解的 class,包括難以捉摸的`this`
## 3. 注意事項
* 只能在函數最外層調用 Hook。不要在循環、條件判斷或者子函數中調用。
* 只能在 React 的函數組件中調用 Hook。不要在其他 JavaScript 函數中調用
## 4\. useState
* useState 就是一個 Hook
* 通過在函數組件里調用它來給組件添加一些內部 state,React 會在重復渲染時保留這個 state
* useState 會返回一對值:當前狀態和一個讓你更新它的函數,你可以在事件處理函數中或其他一些地方調用這個函數。它類似 class 組件的 this.setState,但是它不會把新的 state 和舊的 state 進行合并
* useState 唯一的參數就是初始 state
* 返回一個 state,以及更新 state 的函數
* 在初始渲染期間,返回的狀態 (state) 與傳入的第一個參數 (initialState) 值相同
* setState 函數用于更新 state。它接收一個新的 state 值并將組件的一次重新渲染加入隊列
~~~
const [state, setState] = useState(initialState);
~~~
### 4.1 計數器
~~~
import React,{useState} from 'react';
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
number: 0
};
}
render() {
return (
<div>
<p>{this.state.number}</p>
<button onClick={() => this.setState({ number: this.state.number + 1 })}>
+
</button>
</div>
);
}
}
function Counter2(){
const [number,setNumber] = useState(0);
return (
<>
<p>{number}</p>
<button onClick={()=>setNumber(number+1)}>+</button>
</>
)
}
export default Counter2;
~~~
### 4.2 每次渲染都是獨立的閉包
* 每一次渲染都有它自己的 Props and State
* 每一次渲染都有它自己的事件處理函數
* alert會“捕獲”我點擊按鈕時候的狀態。
* 我們的組件函數每次渲染都會被調用,但是每一次調用中number值都是常量,并且它被賦予了當前渲染中的狀態值
* 在單次渲染的范圍內,props和state始終保持不變
* [making-setinterval-declarative-with-react-hooks](https://overreacted.io/making-setinterval-declarative-with-react-hooks/)
~~~
function Counter2(){
const [number,setNumber] = useState(0);
function alertNumber(){
setTimeout(()=>{
alert(number);
},3000);
}
return (
<>
<p>{number}</p>
<button onClick={()=>setNumber(number+1)}>+</button>
<button onClick={alertNumber}>alertNumber</button>
</>
)
}
~~~
~~~
function Counter() {
const [number, setNumber] = useState(0);
const savedCallback = useRef();
function alertNumber() {
setTimeout(() => {
alert(savedCallback.current);
}, 3000);
}
return (
<>
<p>{number}</p>
<button onClick={() => {
setNumber(number + 1);
savedCallback.current = number + 1;
}}>+</button>
<button onClick={alertNumber}>alertNumber</button>
</>
)
}
~~~
### 4.3 函數式更新
* 如果新的 state 需要通過使用先前的 state 計算得出,那么可以將函數傳遞給 setState。該函數將接收先前的 state,并返回一個更新后的值
~~~
function Counter2(){
const [number,setNumber] = useState(0);
let numberRef = useRef(number);
numberRef.current = number;
function alertNumber(){
setTimeout(()=>{
alert(numberRef.current);
},3000);
}
+ function lazy(){
+ setTimeout(()=>{
+ setNumber(number+1);
+ },3000);
+ }
+ function lazyFunc(){
+ setTimeout(()=>{
+ setNumber(number=>number+1);
+ },3000);
+ }
return (
<>
<p>{number}</p>
<button onClick={()=>setNumber(number+1)}>+</button>
<button onClick={lazy}>lazy+</button>
<button onClick={lazyFunc}>lazyFunc+</button>
<button onClick={alertNumber}>alertNumber</button>
</>
)
}
~~~
### 4.4 惰性初始 state
* initialState 參數只會在組件的初始渲染中起作用,后續渲染時會被忽略
* 如果初始 state 需要通過復雜計算獲得,則可以傳入一個函數,在函數中計算并返回初始的 state,此函數只在初始渲染時被調用
* 與 class 組件中的 setState 方法不同,useState 不會自動合并更新對象。你可以用函數式的 setState 結合展開運算符來達到合并更新對象的效果
~~~
function Counter3(){
const [{name,number},setValue] = useState(()=>{
return {name:'計數器',number:0};
});
return (
<>
<p>{name}:{number}</p>
<button onClick={()=>setValue({number:number+1})}>+</button>
</>
)
}
~~~
### 4.5 性能優化
#### 4.5.1 Object.is
* 調用 State Hook 的更新函數并傳入當前的 state 時,React 將跳過子組件的渲染及 effect 的執行。(React 使用 Object.is 比較算法 來比較 state。)
~~~
function Counter4(){
const [counter,setCounter] = useState({name:'計數器',number:0});
console.log('render Counter')
return (
<>
<p>{counter.name}:{counter.number}</p>
<button onClick={()=>setCounter({...counter,number:counter.number+1})}>+</button>
<button onClick={()=>setCounter(counter)}>-</button>
</>
)
}
~~~
#### 4.5.2 減少渲染次數
* 把內聯回調函數及依賴項數組作為參數傳入`useCallback`,它將返回該回調函數的 memoized 版本,該回調函數僅在某個依賴項改變時才會更新
* 把創建函數和依賴項數組作為參數傳入`useMemo`,它僅會在某個依賴項改變時才重新計算 memoized 值。這種優化有助于避免在每次渲染時都進行高開銷的計算
~~~
function Child({onButtonClick,data}){
console.log('Child render');
return (
<button onClick={onButtonClick} >{data.number}</button>
)
}
Child = memo(Child);
function App(){
const [number,setNumber] = useState(0);
const [name,setName] = useState('zhufeng');
const addClick = useCallback(()=>setNumber(number+1),[number]);
const data = useMemo(()=>({number}),[number]);
return (
<div>
<input type="text" value={name} onChange={e=>setName(e.target.value)}/>
<Child onButtonClick={addClick} data={data}/>
</div>
)
}
~~~
### 4.6 注意事項
* 只能在函數最外層調用 Hook。不要在循環、條件判斷或者子函數中調用。
~~~
import React, { useEffect, useState, useReducer } from 'react';
import ReactDOM from 'react-dom';
function App() {
const [number, setNumber] = useState(0);
const [visible, setVisible] = useState(false);
if (number % 2 == 0) {
useEffect(() => {
setVisible(true);
}, [number]);
} else {
useEffect(() => {
setVisible(false);
}, [number]);
}
return (
<div>
<p>{number}</p>
<p>{visible && <div>visible</div>}</p>
<button onClick={() => setNumber(number + 1)}>+</button>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
~~~
## 5\. useReducer
* useState 的替代方案。它接收一個形如 (state, action) => newState 的 reducer,并返回當前的 state 以及與其配套的 dispatch 方法
* 在某些場景下,useReducer 會比 useState 更適用,例如 state 邏輯較復雜且包含多個子值,或者下一個 state 依賴于之前的 state 等
### 5.1 基本用法
~~~
const [state, dispatch] = useReducer(reducer, initialArg, init);
~~~
~~~
const initialState = 0;
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {number: state.number + 1};
case 'decrement':
return {number: state.number - 1};
default:
throw new Error();
}
}
function init(initialState){
return {number:initialState};
}
function Counter(){
const [state, dispatch] = useReducer(reducer, initialState,init);
return (
<>
Count: {state.number}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
)
}
~~~
## 6\. useContext
* 接收一個 context 對象(React.createContext 的返回值)并返回該 context 的當前值
* 當前的 context 值由上層組件中距離當前組件最近的 的 value prop 決定
* 當組件上層最近的 更新時,該 Hook 會觸發重渲染,并使用最新傳遞給 MyContext provider 的 context value 值
* useContext(MyContext) 相當于 class 組件中的`static contextType = MyContext`或者`<MyContext.Consumer>`
* useContext(MyContext) 只是讓你能夠讀取 context 的值以及訂閱 context 的變化。你仍然需要在上層組件樹中使用 來為下層組件提供 context
~~~
const CounterContext = React.createContext();
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {number: state.number + 1};
case 'decrement':
return {number: state.number - 1};
default:
throw new Error();
}
}
function Counter(){
let {state,dispatch} = useContext(CounterContext);
return (
<>
<p>{state.number}</p>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
)
}
function App(){
const [state, dispatch] = useReducer(reducer, {number:0});
return (
<CounterContext.Provider value={{state,dispatch}}>
<Counter/>
</CounterContext.Provider>
)
}
~~~
## 7\. effect
* 在函數組件主體內(這里指在 React 渲染階段)改變 DOM、添加訂閱、設置定時器、記錄日志以及執行其他包含副作用的操作都是不被允許的,因為這可能會產生莫名其妙的 bug 并破壞 UI 的一致性
* 使用 useEffect 完成副作用操作。賦值給 useEffect 的函數會在組件渲染到屏幕之后執行。你可以把 effect 看作從 React 的純函數式世界通往命令式世界的逃生通道
* useEffect 就是一個 Effect Hook,給函數組件增加了操作副作用的能力。它跟 class 組件中的`componentDidMount`、`componentDidUpdate`和`componentWillUnmount`具有相同的用途,只不過被合并成了一個 API
* 該 Hook 接收一個包含命令式、且可能有副作用代碼的函數
~~~
useEffect(didUpdate);
~~~
### 7.1 通過class實現修標題
~~~
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
number: 0
};
}
componentDidMount() {
document.title = `你點擊了${this.state.number}次`;
}
componentDidUpdate() {
document.title = `你點擊了${this.state.number}次`;
}
render() {
return (
<div>
<p>{this.state.number}</p>
<button onClick={() => this.setState({ number: this.state.number + 1 })}>
+
</button>
</div>
);
}
}
~~~
> 在這個 class 中,我們需要在兩個生命周期函數中編寫重復的代碼,這是因為很多情況下,我們希望在組件加載和更新時執行同樣的操作。我們希望它在每次渲染之后執行,但 React 的 class 組件沒有提供這樣的方法。即使我們提取出一個方法,我們還是要在兩個地方調用它。useEffect會在第一次渲染之后和每次更新之后都會執行
### 7.2 通過effect實現
~~~
import React,{Component,useState,useEffect} from 'react';
import ReactDOM from 'react-dom';
function Counter(){
const [number,setNumber] = useState(0);
// 相當于 componentDidMount 和 componentDidUpdate:
useEffect(() => {
// 使用瀏覽器的 API 更新頁面標題
document.title = `你點擊了${number}次`;
});
return (
<>
<p>{number}</p>
<button onClick={()=>setNumber(number+1)}>+</button>
</>
)
}
ReactDOM.render(<Counter />, document.getElementById('root'));
~~~
> 每次我們重新渲染,都會生成新的 effect,替換掉之前的。某種意義上講,effect 更像是渲染結果的一部分 —— 每個 effect 屬于一次特定的渲染。
### 7.3 跳過 Effect 進行性能優化
* 如果某些特定值在兩次重渲染之間沒有發生變化,你可以通知 React 跳過對 effect 的調用,只要傳遞數組作為 useEffect 的第二個可選參數即可
* 如果想執行只運行一次的 effect(僅在組件掛載和卸載時執行),可以傳遞一個空數組(\[\])作為第二個參數。這就告訴 React 你的 effect 不依賴于 props 或 state 中的任何值,所以它永遠都不需要重復執行
~~~
function Counter(){
const [number,setNumber] = useState(0);
// 相當于componentDidMount 和 componentDidUpdate
useEffect(() => {
console.log('開啟一個新的定時器')
const $timer = setInterval(()=>{
setNumber(number=>number+1);
},1000);
},[]);
return (
<>
<p>{number}</p>
</>
)
}
~~~
### 7.4 清除副作用
* 副作用函數還可以通過返回一個函數來指定如何清除副作用
* 為防止內存泄漏,清除函數會在組件卸載前執行。另外,如果組件多次渲染,則在執行下一個 effect 之前,上一個 effect 就已被清除
~~~
import React, { useEffect, useState, useReducer } from 'react';
import ReactDOM from 'react-dom';
function Counter() {
const [number, setNumber] = useState(0);
useEffect(() => {
console.log('開啟一個新的定時器')
const $timer = setInterval(() => {
setNumber(number => number + 1);
}, 1000);
return () => {
console.log('銷毀老的定時器');
clearInterval($timer);
}
});
return (
<>
<p>{number}</p>
</>
)
}
function App() {
let [visible, setVisible] = useState(true);
return (
<div>
{visible && <Counter />}
<button onClick={() => setVisible(false)}>stop</button>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
~~~
### 7.5 useRef
* useRef 返回一個可變的 ref 對象,其`.current`屬性被初始化為傳入的參數(initialValue)
* 返回的 ref 對象在組件的整個生命周期內保持不變
~~~
const refContainer = useRef(initialValue);
~~~
#### 7.5.1 useRef
~~~
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
function Parent() {
let [number, setNumber] = useState(0);
return (
<>
<Child />
<button onClick={() => setNumber({ number: number + 1 })}>+</button>
</>
)
}
let input;
function Child() {
const inputRef = useRef();
console.log('input===inputRef', input === inputRef);
input = inputRef;
function getFocus() {
inputRef.current.focus();
}
return (
<>
<input type="text" ref={inputRef} />
<button onClick={getFocus}>獲得焦點</button>
</>
)
}
ReactDOM.render(<Parent />, document.getElementById('root'));
~~~
#### 7.5.2 forwardRef
* 將ref從父組件中轉發到子組件中的dom元素上
* 子組件接受props和ref作為參數
~~~
function Child(props,ref){
return (
<input type="text" ref={ref}/>
)
}
Child = forwardRef(Child);
function Parent(){
let [number,setNumber] = useState(0);
const inputRef = useRef();
function getFocus(){
inputRef.current.value = 'focus';
inputRef.current.focus();
}
return (
<>
<Child ref={inputRef}/>
<button onClick={()=>setNumber({number:number+1})}>+</button>
<button onClick={getFocus}>獲得焦點</button>
</>
)
}
~~~
#### 7.5.3 useImperativeHandle
* `useImperativeHandle`可以讓你在使用 ref 時自定義暴露給父組件的實例值
* 在大多數情況下,應當避免使用 ref 這樣的命令式代碼。useImperativeHandle 應當與 forwardRef 一起使用
~~~
function Child(props,ref){
const inputRef = useRef();
useImperativeHandle(ref,()=>(
{
focus(){
inputRef.current.focus();
}
}
));
return (
<input type="text" ref={inputRef}/>
)
}
Child = forwardRef(Child);
function Parent(){
let [number,setNumber] = useState(0);
const inputRef = useRef();
function getFocus(){
console.log(inputRef.current);
inputRef.current.value = 'focus';
inputRef.current.focus();
}
return (
<>
<Child ref={inputRef}/>
<button onClick={()=>setNumber({number:number+1})}>+</button>
<button onClick={getFocus}>獲得焦點</button>
</>
)
}
~~~
## 8\. useLayoutEffect
* 其函數簽名與 useEffect 相同,但它會在所有的 DOM 變更之后同步調用 effect
* 可以使用它來讀取 DOM 布局并同步觸發重渲染
* 在瀏覽器執行繪制之前useLayoutEffect內部的更新計劃將被同步刷新
* 盡可能使用標準的 useEffect 以避免阻塞視圖更新

~~~
function LayoutEffect() {
const [color, setColor] = useState('red');
useLayoutEffect(() => {
alert(color);
});
useEffect(() => {
console.log('color', color);
});
return (
<>
<div id="myDiv" style={{ background: color }}>顏色</div>
<button onClick={() => setColor('red')}>紅</button>
<button onClick={() => setColor('yellow')}>黃</button>
<button onClick={() => setColor('blue')}>藍</button>
</>
);
}
~~~
## 9\. 自定義 Hook
* 有時候我們會想要在組件之間重用一些狀態邏輯
* 自定義 Hook 可以讓你在不增加組件的情況下達到同樣的目的
* Hook 是一種復用狀態邏輯的方式,它不復用 state 本身
* 事實上 Hook 的每次調用都有一個完全獨立的 state
* 自定義 Hook 更像是一種約定,而不是一種功能。如果函數的名字以 use 開頭,并且調用了其他的 Hook,則就稱其為一個自定義 Hook
### 9.1.自定義計數器
~~~
function useNumber(){
const [number,setNumber] = useState(0);
useEffect(() => {
console.log('開啟一個新的定時器')
const $timer = setInterval(()=>{
setNumber(number+1);
},1000);
return ()=>{
console.log('銷毀老的定時器')
clearInterval($timer);
}
});
return number;
}
function Counter1(){
let number1 = useNumber();
return (
<>
<p>{number1}</p>
</>
)
}
function Counter2(){
let number = useNumber();
return (
<>
<p>{number}</p>
</>
)
}
function App(){
return <><Counter1/><Counter2/></>
}
~~~
### 9.2 中間件
#### 9.2.1 logger
~~~
import React, { useEffect, useState, useReducer } from 'react';
import ReactDOM from 'react-dom';
const initialState = 0;
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { number: state.number + 1 };
case 'decrement':
return { number: state.number - 1 };
default:
throw new Error();
}
}
function init(initialState) {
return { number: initialState };
}
function useLogger(reducer, initialState, init) {
const [state, dispatch] = useReducer(reducer, initialState, init);
let dispatchWithLogger = (action) => {
console.log('老狀態', state);
dispatch(action);
}
useEffect(function () {
console.log('新狀態', state);
}, [state]);
return [state, dispatchWithLogger];
}
function Counter() {
const [state, dispatch] = useLogger(reducer, initialState, init);
return (
<>
Count: {state.number}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</>
)
}
ReactDOM.render(<Counter />, document.getElementById('root'));
~~~
#### 9.2.2 promise
~~~
import React, { useEffect, useState, useReducer } from 'react';
import ReactDOM from 'react-dom';
const initialState = 0;
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { number: state.number + 1 };
case 'decrement':
return { number: state.number - 1 };
default:
throw new Error();
}
}
function init(initialState) {
return { number: initialState };
}
function useLogger(reducer, initialState, init) {
const [state, dispatch] = useReducer(reducer, initialState, init);
let dispatchWithLogger = (action) => {
console.log('老狀態', state);
dispatch(action);
}
useEffect(function () {
console.log('新狀態', state);
}, [state]);
return [state, dispatchWithLogger];
}
function usePromise(reducer, initialState, init) {
const [state, dispatch] = useReducer(reducer, initialState, init);
let dispatchPromise = (action) => {
if (action.payload && action.payload.then) {
action.payload.then((payload) => dispatch({ ...action, payload }));
} else {
dispatch(action);
}
}
return [state, dispatchPromise];
}
function Counter() {
const [state, dispatch] = usePromise(reducer, initialState, init);
return (
<>
Count: {state.number}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({
type: 'increment',
payload: new Promise(resolve => {
setTimeout(resolve, 1000);
})
})}>delay</button>
</>
)
}
ReactDOM.render(<Counter />, document.getElementById('root'));
~~~
#### 9.2.3 thunk
~~~
import React, { useEffect, useState, useReducer } from 'react';
import ReactDOM from 'react-dom';
import { resolve } from 'dns';
const initialState = 0;
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { number: state.number + 1 };
case 'decrement':
return { number: state.number - 1 };
default:
throw new Error();
}
}
function init(initialState) {
return { number: initialState };
}
function useLogger(reducer, initialState, init) {
const [state, dispatch] = useReducer(reducer, initialState, init);
let dispatchWithLogger = (action) => {
console.log('老狀態', state);
dispatch(action);
}
useEffect(function () {
console.log('新狀態', state);
}, [state]);
return [state, dispatchWithLogger];
}
function usePromise(reducer, initialState, init) {
const [state, dispatch] = useReducer(reducer, initialState, init);
let dispatchPromise = (action) => {
if (action.payload && action.payload.then) {
action.payload.then((payload) => dispatch({ ...action, payload }));
} else {
dispatch(action);
}
}
return [state, dispatchPromise];
}
function useThunk(reducer, initialState, init) {
const [state, dispatch] = useReducer(reducer, initialState, init);
let dispatchPromise = (action) => {
if (typeof action === 'function') {
action(dispatchPromise, () => state);
} else {
dispatch(action)
}
}
return [state, dispatchPromise];
}
function Counter() {
const [state, dispatch] = useThunk(reducer, initialState, init);
return (
<>
Count: {state.number}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch(function (dispatch, getState) {
setTimeout(function () {
dispatch({ type: 'increment' });
}, 1000);
})}>delay</button>
</>
)
}
ReactDOM.render(<Counter />, document.getElementById('root'));
~~~
### 9.3 ajax
~~~
import React, { useState, useEffect, useLayoutEffect } from 'react';
import ReactDOM from 'react-dom';
function useRequest(url) {
let limit = 5;
let [offset, setOffset] = useState(0);
let [data, setData] = useState([]);
function loadMore() {
setData(null);
fetch(`${url}?offset=${offset}&limit=${limit}`)
.then(response => response.json())
.then(pageData => {
setData([...data, ...pageData]);
setOffset(offset + pageData.length);
});
}
useEffect(loadMore, []);
return [data, loadMore];
}
function App() {
const [users, loadMore] = useRequest('http://localhost:8000/api/users');
if (users === null) {
return <div>正在加載中....</div>
}
return (
<>
<ul>
{
users.map((item, index) => <li key={index}>{item.id}:{item.name}</li>)
}
</ul>
<button onClick={loadMore}>加載更多</button>
</>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
~~~
async+await
~~~
import React, { useState, useEffect, useLayoutEffect } from 'react';
import ReactDOM from 'react-dom';
function useRequest(url) {
let limit = 5;
let [offset, setOffset] = useState(0);
let [data, setData] = useState([]);
async function loadMore() {
setData(null);
let pageData = await fetch(`${url}?offset=${offset}&limit=${limit}`)
.then(response => response.json());
setData([...data, ...pageData]);
setOffset(offset + pageData.length);
}
useEffect(loadMore, []);
return [data, loadMore];
}
function App() {
const [users, loadMore] = useRequest('http://localhost:8000/api/users');
if (users === null) {
return <div>正在加載中....</div>
}
return (
<>
<ul>
{
users.map((item, index) => <li key={index}>{item.id}:{item.name}</li>)
}
</ul>
<button onClick={loadMore}>加載更多</button>
</>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
~~~
~~~
let express = require('express');
let app = express();
app.use(function (req, res, next) {
res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
next();
});
app.get('/api/users', function (req, res) {
let offset = parseInt(req.query.offset);
let limit = parseInt(req.query.limit);
let result = [];
for (let i = offset; i < offset + limit; i++) {
result.push({ id: i + 1, name: 'name' + (i + 1) });
}
res.json(result);
});
app.listen(8000);
~~~
### 9.4 動畫
~~~
import React, { useState, useEffect, useLayoutEffect } from 'react';
import ReactDOM from 'react-dom';
import './index.css';
function useMove(initialClassName) {
const [className, setClassName] = useState(initialClassName);
const [state, setState] = useState('');
function start() {
setState('bigger');
}
useEffect(() => {
if (state === 'bigger') {
setClassName(`${initialClassName} ${initialClassName}-bigger`);
}
}, [state]);
return [className, start];
}
function App() {
const [className, start] = useMove('circle');
return (
<div>
<button onClick={start}>start</button>
<div className={className}></div>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
~~~
~~~
.circle {
width : 50px;
height : 50px;
border-radius: 50%;
background : red;
transition: all .5s;
}
.circle-bigger {
width : 200px;
height : 200px;
}
~~~
- 文檔簡介
- 基礎面試題【珠峰2019.8】
- P01_call,aplly區別
- P02_綜合面試題講解2-2
- P03_箭頭函數和普通函數區別-綜合面試題講解2-3
- P05_實現indexOf
- P06_綜合面試題講解2-6
- P07_URL解析題
- P08_原型題
- P09_圖片延時加載
- P10_正則-包含數字字母下劃線
- P11_綜合面試題講解2-11
- P12_英文字母加空格
- P13_數組扁平化并去重
- P14_模擬實現new
- P15_合并數組
- P16_定時器,打印012345
- P17_匿名函數輸出值問題
- P18_a在什么情況下打印輸出+1+1+1
- P19_對數組的理解
- P20_冒泡排序
- P21_插入排序
- P22_快速排序
- P23_銷售額存在對象中
- P24_求數組的交集
- P25_旋轉數組
- P26_ [函數柯理化思想]
- P27_ [柯理化函數的遞歸]
- 網絡協議【珠峰2019.6】
- TypeScript+Axios入門+實戰【珠峰2019.11】
- 1.數據結構
- 2.函數和繼承
- 3.裝飾器
- 4.抽象類-接口-泛型
- 05-結構類型系統和類型保護
- 06-類型變換
- AST-抽象語法樹
- React性能優化【珠峰2019.10】
- 1-react性能優化
- 2-react性能優化
- 3.react-immutable
- React Hooks【珠峰2019.12】
- 前端框架及項目面試
- 第07章 React 使用
- 7-1 React使用-考點串講
- 7-2 JSX基本知識點串講
- 7-3 JSX如何判斷條件和渲染列表
- 7-4 React事件為何bind this
- 7-5 React事件和DOM事件的區別
- 7-6 React表單知識點串講
- 7-7 React父子組件通訊
- 7-8 setState為何使用不可變值
- 7-9 setState是同步還是異步
- 7-10 setState合適會合并state
- 7-11 React組件生命周期
- 7-12 React基本使用-知識點總結和復習
- 7-13 React函數組件和class組件有何區別
- 7-14 什么是React非受控組件
- 7-15 什么場景需要用React Portals
- 7-16 是否用過React Context
- 7-17 React如何異步加載組件
- 7-18 React性能優化-SCU的核心問題在哪里
- 7-19 React性能優化-SCU默認返回什么
- 7-20 React性能優化-SCU一定要配合不可變值
- 7-21 React性能優化-PureComponent和memo
- 7-22 React性能優化-了解immutable.js
- 7-23 什么是React高階組件
- 7-24 什么是React Render Props
- 7-25 React高級特性考點總結
- 7-26 Redux考點串講
- 7-27 描述Redux單項數據流
- 7-28 串講react-redux知識點
- 7-29 Redux action如何處理異步
- 7-30 簡述Redux中間件原理
- 7-31 串講react-router知識點
- 7-32 React使用-考點總結
- 第08章 React 原理
- 8-1 React原理-考點串講
- 8-2 再次回顧不可變值
- 8-3 vdom和diff是實現React的核心技術
- 8-4 JSX本質是什么
- 8-5 說一下React的合成事件機制
- 8-6 說一下React的batchUpdate機制
- 8-7 簡述React事務機制
- 8-8 說一下React組件渲染和更新的過程
- 8-9 React-fiber如何優化性能
- 第09章 React 面試真題演練
- 9-1 React真題演練-1-組件之間如何通訊
- 9-2 React真題演練-2-ajax應該放在哪個生命周期
- 9-3 React真題演練-3-組件公共邏輯如何抽離
- 9-4 React真題演練-4-React常見性能優化方式
- 9-5 React真題演練-5-React和Vue的區別
- 第10章 webpack 和 babel
- 10-1 webpack考點梳理
- 10-2 webpack基本配置串講(上)
- 10-3 webpack基本配置串講(下)
- 10-4 webpack如何配置多入口
- 10-5 webpack如何抽離壓縮css文件
- 10-6 webpack如何抽離公共代碼和第三方代碼
- 10-7 webpack如何實現異步加載JS
- 10-8 module chunk bundle 的區別
- 10-9 webpack優化構建速度-知識點串講
- 10-11 happyPack是什么
- 10-12 webpack如何配置熱更新
- 10-13 何時使用DllPlugin
- 10-14 webpack優化構建速度-考點總結和復習
- 10-15 webpack優化產出代碼-考點串講
- 10-16 什么是Tree-Shaking
- 10-17 ES Module 和 Commonjs 的區別
- 10-18 什么是Scope Hostin
- 10-19 babel基本概念串講
- 10-20 babel-polyfill是什么
- 10-21 babel-polyfill如何按需引入
- 10-22 babel-runtime是什么
- 10-23 webpack考點總結和復習
- 10-24 webpack面試真題-前端代碼為何要打包
- 10-25 webpack面試真題-為何Proxy不能被Polyfill
- 10-26 webpack面試真題-常見性能優化方法