用心阅读,跟随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 在项目中的实践》
相关阅读:
舒适提示:**多伸懒腰,对身体好