用心閱讀,跟隨codesandbox demo或運行源碼,你將熟悉react各類組件的優缺點及用法,完全熟悉react hook的用法,收益應該不小😀😀😀javascript
大綱:html
Functional (Stateless) Component,功能組件也叫無狀態組件,通常只負責渲染。java
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
複製代碼
Class (Stateful) Component,類組件也是有狀態組件,也能夠叫容器組件。通常有交互邏輯和業務邏輯。react
class Welcome extends React.Component {
state = {
name: ‘tori’,
}
componentDidMount() {
fetch(…);
…
}
render() {
return (
<> <h1>Hello, {this.state.name}</h1> <button onClick={() => this.setState({name: ‘007’})}>更名</button> </> ); } } 複製代碼
Presentational Component,和功能(無狀態)組件相似。git
const Hello = (props) => {
return (
<div> <h1>Hello! {props.name}</h1> </div>
)
}
複製代碼
**📢 總結: **github
經過以上組件之間的組合能實現絕大部分需求。json
Higher order components (HOC)redux
HOC 主要是抽離狀態,將重複的受控組件的邏輯抽離到高階組件中,以新的props傳給受控組件中,高階組件中能夠操做props傳入受控組件。 開源庫中常見的高階組件:Redux的connect, react-router的withRouter等等。api
Class HocFactory extends React.Component {
constructor(props) {
super(props)
}
// 操做props
// …
render() {
const newProps = {…};
return (Component) => <Component {…newProps} />;
}
}
Const Authorized = (Component) => (permission) => {
return Class Authorized extends React.Component {
…
render() {
const isAuth = ‘’;
return isAuth ? <Component /> : <NoMatch />; } } } // 項目中涉及到的高階組件 // 主要做用是將全部action經過高階組件代理到component的Pro上。 import { bindActionCreators } from ‘redux’; import { connect } from ‘react-redux'; // 全部頁面action集合 import * as actions from './actions'; // 緩存actions, 避免render從新加載 let cachedActions; // action經過bindActionCreators綁定dispatch, const bindActions = (dispatch, ownProps) => { if (!cachedActions) { cachedActions = { dispatch, actions: bindActionCreators(actions, dispatch), }; } return cachedActions; }; const connectWithActions = ( mapStateToProps, mergeProps, options ) => (component) => connect( mapStateToProps, bindActions, mergeProps, options )(component); export default connectWithActions; // 相似還有log中間件樣子的等等。 複製代碼
Render Props 你能夠把它理解成 JavaScript 中的回調函數數組
// 實現一個控制modal visible的高階組件
class ToggleVisible extends React.Component {
state = {
visible: false
};
toggle = () => {
this.setState({visible: !this.state.visible});
}
render() {
return (
<>{this.props.children({visible, toggle})}</>
);
}
}
//使用
const EditUser = () => (
<ToggleVisible>
{({visible, toggle}) => (<>
<Modal visible={visible}/>
<Button onClick={toggle}>打開/關閉modal</Button>
</>)}
</ToggleVisible>
)
複製代碼
子組件所須要的props在父組件會封裝好,引用子組件的時候就不必傳遞全部props了。組合組件核心的兩個方法是React.Children.map和React.cloneElement。
例以下面 子組件須要的click事件轉移到了父組件,經過父組件內部封裝到子組件上,ant-design的不少group組件用到了此方法。
class GroupButton extends React.PureComponent {
state = {
activeIndex: 0
};
render() {
return (
<> {React.Children.map(this.props.children, (child, index) => child.type ? React.cloneElement(child, { active: this.state.activeIndex === index, onClick: () => { this.setState({ activeIndex: index }); this.props.onChange(child.props.value); } }) : child )} </> ); } } // 用法 <GroupButton onChange={e => { console.log(「onChange」, e); }} > <Button value="red">red</Button> <Button value="yellow">yellow</Button> <Button value=「blue」>blue</Button> <Button value="white">white</Button> </GroupButton> 複製代碼
Hook 出現以前,組件之間複用狀態邏輯很難,解決方案(HOC、Render Props)都須要從新組織組件結構, 且代碼難以理解。在React DevTools 中觀察過 React 應用,你會發現由 providers,consumers,高階組件,render props 等其餘抽象層組成的組件會造成「嵌套地獄」。
組件維護愈來愈複雜,譬如事件監聽邏輯要在不一樣的生命週期中綁定和解綁,複雜的頁面componentDidMount包涵不少邏輯,代碼閱讀性變得不好。
class組件中的this難以理解,且class 不能很好的壓縮,而且會使熱重載出現不穩定的狀況。更多引子介紹參見官方介紹。
因此hook就爲解決這些問題而來:
下面逐一介紹官方提供的hook API。
📢 函數組件有狀態了
const [state, setState] = useState(initialState);
state爲變量,setState
修改 state值的方法, setState也是異步執行。
class this.setState更新是state是合併, useState中setState是替換。
function Example() {
// 聲明一個叫 "count" 的 state 變量
const [count, setCount] = useState(0);
const [obj, setData] = useState();
return (
<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </div>
);
}
複製代碼
📢 忘記生命週期,記住反作用
useEffect(() => {// Async Action}, ?[dependencies]); // 第二參數非必填
複製代碼
function Hook2() {
const [data, setData] = useState();
useEffect(() => {
console.log("useEffect");
});
return (
<div> {(() => { console.log("render"); return null; })()} <p>data: {JSON.stringify(data)}</p> </div>
);
}
複製代碼
執行結果:
結論:
import React, { useState, useEffect, useRef } from 「react」;
function Demo3() {
const [data, setData] = useState();
useEffect(() => {
console.log("useEffect—[]」);
fetch(「https://www.mxnzp.com/api/lottery/common/latest?code=ssq」)
.then(res => res.json())
.then(res => {
setData(res);
});
}, []);
useEffect(() => {
console.log("useEffect ---> 無依賴");
});
useEffect(() => {
console.log(「useEffect 依賴data: data發生了變化」);
}, [data]);
return (
<div>
<p>data: {JSON.stringify(data)}</p>
</div>
);
}
export default Demo3;
複製代碼
執行結果:
結論:
[]
能夠實現相似componentDidMount
的做用,但最好忘記生命週期, 只記反作用。import React, { useState, useEffect, useRef } from "react";
function Demo4() {
useEffect(() => {
console.log(「useEffect1」);
const timeId = setTimeout(() => {
console.log(「useEffect1-setTimeout-2000」);
}, 2000);
return () => {
clearTimeout(timeId);
};
}, []);
useEffect(() => {
console.log("useEffect2");
const timeId = setInterval(() => {
console.log("useEffect2-setInterval-1000");
}, 1000);
return () => {
clearInterval(timeId);
};
}, []);
return (
<div> {(() => { console.log(「render」); return null; })()} <p>demo4</p> </div>
);
}
export default Demo4;
複製代碼
執行結果:
結論:
componentUnMount
的鉤子函數,通常是remove eventLisenter, clear timeId等,主要是組件卸載後防止內存泄漏。綜上所述,useEffect 就是監聽每當依賴變化時,執行回調函數的存在函數組件中的鉤子函數。
跨組件共享數據的鉤子函數
const value = useContext(MyContext);
// MyContext 爲 context 對象(React.createContext 的返回值)
// useContext 返回MyContext的返回值。
// 當前的 context 值由上層組件中距離當前組件最近的<MyContext.Provider> 的 value prop 決定。
複製代碼
import React, { useContext, useState } from 「react」;
const MyContext = React.createContext();
function Demo5() {
const [value, setValue] = useState("init」);
console.log(「Demo5」);
return (
<div>
{(() => {
console.log("render");
return null;
})()}
<button onClick={() => {
console.log('click:更新value')
setValue(`${Date.now()}_newValue`)
}}>
改變value
</button>
<MyContext.Provider value={value}>
<Child1 />
<Child2 />
</MyContext.Provider>
</div>
);
}
function Child1() {
const value = useContext(MyContext);
console.log(「Child1-value」, value);
return <div>Child1-value: {value}</div>;
}
function Child2(props) {
console.log(‘Child2’)
return <div>Child2</div>;
}
複製代碼
執行結果:
結論:
useContext 的組件總會在 context 值變化時從新渲染, 因此<MyContext.Provider>
包裹的越多,層級越深,性能會形成影響。
<MyContext.Provider>
的 value 發生變化時候, 包裹的組件不管是否訂閱content value,全部組件都會重新渲染。
demo中child2 不該該rerender, 如何避免沒必要要的render?*
使用React.memo優化。
const Child2 = React.memo((props) => {
return <div>Child2</div>;
})
複製代碼
執行結果:
注意: 默認狀況下React.memo只會對複雜對象作淺層對比,若是你想要控制對比過程,那麼請將自定義的比較函數經過第二個參數傳入來實現。 參考連接
const refContainer = useRef(initialValue);
複製代碼
const [state, dispatch] = useReducer(reducer, initialState);
複製代碼
reducer
就是一個只能經過action
將state
從一個過程轉換成另外一個過程的純函數; useReducer
就是一種經過(state,action) => newState
的過程,和redux
工做方式同樣。數據流: dispatch(action) => reducer更新state => 返回更新後的state
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case ‘decrement’:
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<> Count: {state.count} <button onClick={() => dispatch({type: 'decrement'})}>-</button> <button onClick={() => dispatch({type: 'increment'})}>+</button> </> ); } 複製代碼
官方推薦如下場景須要useReducer更佳:
const fetchReducer = (state, action) => {
switch (action.type) {
case 「FETCH_INIT":
return {
...state,
loading: true,
error: false
};
case 「FETCH_SUCCESS」:
return {
...state,
loading: false,
error: false,
data: action.payload
};
case "FETCH_FAIL":
return {
…state,
loading: false,
error: true
};
default:
throw new Error();
}
};
function Demo6() {
const [state, dispatch] = useReducer(fetchReducer, {
loading: false,
error: false,
msg: "",
data: {}
});
const getData = useCallback(async () => {
try {
dispatch({ type: "FETCH_INIT" });
const response = await fetch(
"https://www.mxnzp.com/api/lottery/common/latest?code=ssq"
);
const res = await response.json();
if (res.code) {
dispatch({ type: "FETCH_SUCCESS", payload: res.data });
} else {
dispatch({ type: 「FETCH_FAIL」, payload: res.msg });
}
} catch (error) {
dispatch({ type: 「FETCH_FAIL」, payload: error });
}
}, []);
useEffect(() => {
getData();
}, [getData]);
return (
<Loading loading={state.loading}>
<p>開獎號碼: {state.data.openCode}</p>
</Loading>
);
}
複製代碼
demo6
useReducer處理了多個能夠用useState實現的邏輯,包括loading, error, msg, data
。
useContext 和 useReducer模擬redux管理狀態
import React, { useReducer, useContext } from 「react」;
const ModalContext = React.createContext();
const visibleReducer = (state, action) => {
switch (action.type) {
case 「CREATE」:
return { ...state, ...action.payload };
case "EDIT":
return { ...state, ...action.payload };
default:
return state;
}
};
function Demo7() {
const initModalVisible = {
create: false,
edit: false
};
const [state, dispatch] = useReducer(visibleReducer, initModalVisible);
return (
<ModalContext.Provider value={{ visibles: state, dispatch }}> <Demo7Child /> </ModalContext.Provider> ); } function Demo7Child() { return ( <div> Demo7Child <Detail /> </div> ); } function Detail() { const { visibles, dispatch } = useContext(ModalContext); console.log("contextValue", visibles); return ( <div> <p>create: {`${visibles.create}`}</p> <button onClick={() => dispatch({ type: "CREATE", payload: { create: true } })} > 打開建立modal </button> </div> ); } export default Demo7; 複製代碼
邏輯很清晰的抽離出來,context value中的值不須要在組件中透傳,即用即取。DEMO7
注意 React 會確保 dispatch 函數的標識是穩定的,而且不會在組件從新渲染時改變。這就是爲何能夠安全地從 useEffect 或 useCallback 的依賴列表中省略 dispatch。
語法:
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
複製代碼
返回一個 memoized 回調函數。
useCallback
解決了什麼問題?先看DEMO8
import React, { useRef, useEffect, useState, useCallback } from 「react」;
function Child({ event, data }) {
console.log("child-render");
// 第五版
useEffect(() => {
console.log(「child-useEffect」);
event();
}, [event]);
return (
<div> <p>child</p> {/* <p>props-data: {data.data && data.data.openCode}</p> */} <button onClick={event}>調用父級event</button> </div>
);
}
const set = new Set();
function Demo8() {
const [count, setCount] = useState(0);
const [data, setData] = useState({});
// 初版
// const handle = async () => {
// const response = await fetch(
// "https://www.mxnzp.com/api/lottery/common/latest?code=ssq"
// );
// const res = await response.json();
// console.log("handle", data);
// setData(res);
// };
// 第二版
// const handle = useCallback(async () => {
// const response = await fetch(
// 「https://www.mxnzp.com/api/lottery/common/latest?code=ssq"
// );
// const res = await response.json();
// console.log(「handle」, data);
// setData(res);
// });
// 第三版
// const handle = useCallback(async () => {
// const response = await fetch(
// 「https://www.mxnzp.com/api/lottery/common/latest?code=ssq」
// );
// const res = await response.json();
// setData(res);
// console.log(「useCallback」, data);
// // eslint-disable-next-line react-hooks/exhaustive-deps
// }, []);
// // 第四版
// const handle = useCallback(async () => {
// const response = await fetch(
// 「https://www.mxnzp.com/api/lottery/common/latest?code=ssq"
// );
// const res = await response.json();
// setData(res);
// console.log(「parent-useCallback", data);
// // eslint-disable-next-line react-hooks/exhaustive-deps
// }, []);
// 第五版
const handle = useCallback(async () => {
const response = await fetch(
"https://www.mxnzp.com/api/lottery/common/latest?code=ssq"
);
const res = await response.json();
setData(res);
console.log("parent-useCallback", data);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [count]);
set.add(handle);
console.log(「parent-render====>」, data);
return (
<div> <button onClick={e => { setCount(count + 1); }} > count++ </button> <p>set size: {set.size}</p> <p>count:{count}</p> <p>data: {data.data && data.data.openCode}</p> <p>-------------------------------</p> <Child event={handle} /> </div> ); } export default Demo8; 複製代碼
結論:
語法: const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
; 返回一個 memoized 值,和useCallback
同樣,當依賴項發生變化,纔會從新計算 memoized 的值,。 useMemo和useCallback不一樣之處是:它容許你將 memoized 應用於任何值類型(不只僅是函數)。 DEMO9
import React, { useState, useMemo } from 「react」;
function Demo9() {
const [count, setCount] = useState(0);
const handle = () => {
console.log(「handle」, count);
return count;
};
const handle1 = useMemo(() => {
console.log("handle1", count);
return count;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handle2 = useMemo(() => {
console.log(「handle2」, count);
// 大計算量的方法
return count;
}, [count]);
console.log("render-parent");
return (
<div> <p> demo9: {count} <button onClick={() => setCount(count + 1)}>++count</button> </p> <p>-------------------</p> <Child handle={handle1} /> </div> ); } function Child({ handle }) { console.log("render-child"); return ( <div> <p>child</p> <p>props-data: {handle}</p> </div> ); } export default Demo9; 複製代碼
總結:
useMemo
會在render
前執行。useMemo
用於返回memoize
,防止每次render時大計算量帶來的開銷。useMemo
優化需謹慎, 由於優化自己也帶來了計算,大多數時候,你不須要考慮去優化沒必要要的從新渲染。// ref:須要傳遞的ref
// createHandle: 須要暴露給父級的方法。
// deps: 依賴
useImperativeHandle(ref, createHandle, [deps])
複製代碼
useImperativeHandle
應當與forwardRef
一塊兒使用。先看DEMO10
import React, {
useRef,
forwardRef,
useImperativeHandle,
useEffect,
useState
} from "react";
const Child = forwardRef((props, ref) => {
const inputEl = useRef();
const [value, setVal] = useState("");
// 初版
// useImperativeHandle(ref, () => {
// console.log("useImperativeHandle");
// return {
// value,
// focus: () => inputEl.current.focus()
// };
// });
// 第二版
useImperativeHandle(
ref,
() => {
console.log(「useImperativeHandle");
return {
value,
focus: () => inputEl.current.focus()
};
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
return (
<input
ref={inputEl}
onChange={e => setVal(e.target.value)}
value={value}
{...props}
/>
);
});
function Demo10() {
const inputEl = useRef(null);
useEffect(() => {
console.log(「parent-useEffect」, inputEl.current);
inputEl.current.focus();
}, []);
function click() {
console.log("click:", inputEl.current);
inputEl.current.focus();
}
console.log(「Demo10」, inputEl.current);
return (
<div>
<Child ref={inputEl} />
<button onClick={click}>click focus</button>
</div>
);
}
export default Demo10;
複製代碼
結論:
useImperativeHandle
在當前組件render後執行。useImperativeHandle
都會執行, 且能拿到 state中最新的值, 父組件調用傳入的方法也是最新。[]
,每當rerender時,useImperativeHandle
不會執行,且不會更新到父組件。[value]
, 達到想要的效果。不經常使用, 只能在React Developer Tools看到,詳見官方傳送門 。
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(false);
// 在開發者工具中的這個 Hook 旁邊顯示標籤
// e.g. "FriendStatus: Online"
useDebugValue(isOnline ? "Online" : "Offline");
return isOnline;
}
function Demo11() {
const isOnline = useFriendStatus(567);
return <div>朋友是否在線:{isOnline ? "在線" : "離線"}</div>;
}
複製代碼
不多用,與 useEffect
相同,但它會在全部的 DOM 變動以後同步調用 effect, 詳見官方傳送門。
歡迎交流,,,😀🍻🍻😀
《自定義 Hook 在項目中的實踐》
相關閱讀:
舒適提示:**多伸懶腰,對身體好