HOC :高阶组件 纯函数 入参是组件出参也是组件
使用HOC的原因:
-
抽取重复代码,实现组件复用:相同功能组件复用
-
条件渲染,控制组件的渲染逻辑(渲染劫持):权限控制。
-
捕获/劫持被处理组件的生命周期,常见场景:组件渲染性能追踪、日志打点。
HOC 实现方式
属性代理
操作props// 可以通过属性代理,拦截父组件传递过来的porps并进行处理。
// 返回一个无状态的函数组件
function HOC(WrappedComponent) {
const newProps = { type: 'HOC' };
return props => <WrappedComponent {...props} {...newProps}/>;
}
// 返回一个有状态的 class 组件
function HOC(WrappedComponent) {
return class extends React.Component {
render() {
const newProps = { type: 'HOC' };
return <WrappedComponent {...this.props} {...newProps}/>;
}
};
}
抽象state// 通过属性代理无法直接操作原组件的state,可以通过props和cb抽象state
function HOC(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
name: '',
};
this.onChange = this.onChange.bind(this);
}
onChange = (event) => {
this.setState({
name: event.target.value,
})
}
render() {
const newProps = {
name: {
value: this.state.name,
onChange: this.onChange,
},
};
return <WrappedComponent {...this.props} {...newProps} />;
}
};
}
// 使用
@HOC
class Example extends Component {
render() {
return <input name="name" {...this.props.name} />;
}
}
反向继承
使用一个函数接受一个组件作为参数传入,并返回一个继承了该传入组件的类组件,且在返回组件的 render() 方法中返回 super.render() 方法
const HOC = (WrappedComponent) => {
return class extends WrappedComponent {
render() {
return super.render();
}
}
}
属性代理例子
// views/PageA.js
import React from 'react';
import fetchMovieListByType from '../lib/utils';
import MovieList from '../components/MovieList';
class PageA extends React.Component {
state = {
movieList: [],
}
/* ... */
async componentDidMount() {
const movieList = await fetchMovieListByType('comedy');
this.setState({
movieList,
});
}
render() {
return <MovieList data={this.state.movieList} emptyTips="暂无喜剧"/>
}
}
export default PageA;
// views/PageB.js
import React from 'react';
import fetchMovieListByType from '../lib/utils';
import MovieList from '../components/MovieList';
class PageB extends React.Component {
state = {
movieList: [],
}
// ...
async componentDidMount() {
const movieList = await fetchMovieListByType('action');
this.setState({
movieList,
});
}
render() {
return <MovieList data={this.state.movieList} emptyTips="暂无动作片"/>
}
}
export default PageB;
// 冗余代码过多
// HOC
import React from 'react';
const withFetchingHOC = (WrappedComponent, fetchingMethod, defaultProps) => {
return class extends React.Component {
async componentDidMount() {
const data = await fetchingMethod();
this.setState({
data,
});
}
render() {
return (
<WrappedComponent
data={this.state.data}
{...defaultProps}
{...this.props}
/>
);
}
}
}
// 使用:
// views/PageA.js
import React from 'react';
import withFetchingHOC from '../hoc/withFetchingHOC';
import fetchMovieListByType from '../lib/utils';
import MovieList from '../components/MovieList';
const defaultProps = {emptyTips: '暂无喜剧'}
export default withFetchingHOC(MovieList, fetchMovieListByType('comedy'), defaultProps);
// views/PageB.js
import React from 'react';
import withFetchingHOC from '../hoc/withFetchingHOC';
import fetchMovieListByType from '../lib/utils';
import MovieList from '../components/MovieList';
const defaultProps = {emptyTips: '暂无动作片'}
export default withFetchingHOC(MovieList, fetchMovieListByType('action'), defaultProps);;
// views/PageOthers.js
import React from 'react';
import withFetchingHOC from '../hoc/withFetchingHOC';
import fetchMovieListByType from '../lib/utils';
import MovieList from '../components/MovieList';
const defaultProps = {...}
export default withFetchingHOC(MovieList, fetchMovieListByType('some-other-type'), defaultProps);
Hooks
useState
const [tag,setTag] = useState(()=>handleData(1)) 可以是函数 可以是值
setTag(true) 触发组件的re-render
useRef
第一种用法 类似于vue ref获取完整dom内容
const DemoUseRef = ()=>{
const dom = useRef(null)
const handleSubmit = () =>{
console.log(dom.current)//p 获取到p标签
} return <div>
<p ref={dom}> 123</p>
</div>
}
组件内部逻辑数据更新,不触发组件更新
const currentRef = useRef(1)
currentRef.current = 2
useEffect
在某些时间点触发一些方法 不可以使用async await
组件更新挂载完成后Vdom --- DOM 更新 --- useEffect 页面闪动
useEffect(() => {}, []) //[]代表组件初始化执行const Demo = ({ a }) => { const handleResize =()=>{} useEffect(()=>{ const timer = setInterval(()=>console.log(666),1000) window.addEventListener('resize', handleResize) // 此函数用于清除副作用 return function(){ clearInterval(timer) window.removeEventListener('resize', handleResize) } },[ a ]) return (<div></div>)}
useLayoutEffect
组件更新挂载完成后Vdom ---useLayoutEffect --- DOM更新 组件卡顿
useLayoutEffect(() => {
/*我们需要在dom绘制之前,移动dom到制定位置*/
const { x ,y } = getPositon() /* 获取要移动的 x,y坐标 */
animate(target.current,{ x,y })
}, []);
useContext
用来获取父级组件传递过来的context值
export default ()=>{
return <div>
<Context.Provider value={{ name:'aaa' }} >
<DemoContext />
<DemoContext1 />
</Context.Provider>
</div>
} // 用Context.Consumer 方式 const DemoContext1 = ()=>{
return <Context.Consumer> { (value)=> <div> my name is { value.name }</div> }
</Context.Consumer>
}
//用useContext方式const DemoContext = ()=> { const value = useContext(Context) return <div> my name is { value.name }</div>
}
useReducer
const DemoUserReducer = ()=>{ //number 为更新后的state值,dispatchNumber为当前派发函数 const [number,dispatchNumber] = useReducer((state,action)=>{ //return 的值为新的state const {payload,name} = action switch(name){ case 'a': return state + 1 case 'b': return state - 1 case 'c': return payload } return state },0) return <div>{number} <button onClick={()=>dispatchNumber({name:'a'})}>增加</button> </div> }
useMemo
返回cb 的运行结果,有依赖项,根据依赖项是否发生变化决定组件是否更新
const DemoUserMemo = ()=>{
const {number , setNumber} = useState(0);
const newLog = useMemo(() =>{
const log = () = >{
console.log(number)
}
return log
},[]) //[]传入的是依赖项
return <div>
<div onClick={()=>newLog()}>打印</div>
<span onClick={()=>setNumber(number+1)}>+1</span>
</div>
}
useCallback
useCallback 的用法与useState 的用法基本一致 但最后会返回一个函数,用一个变量保存起来
返回的函数a 会根据b的变化而变化,如果b始终未发生变化,a也不会重新生成,避免函数在不必要的情况下更新
const a = useCallback(()=>{
return function(){
console.log(b)
}
},[b])
自定义hooks
//set title
import {useEffect} from 'react'
const useTitle = (title) =>{
useEffect(()=>{
document.title = title
},[])
}
export default useTitle
const App = () =>{
useTitle('new title')
}
// update
import {useState} from 'react'
const useUpdate = ()=>{
const [,setFlag] = useState();
const update = () =>{
setFlag(Date.now())
}
}
const App = () =>{
const update = useUpdate()
<button onClick={update}> update </button>
}
异步组件
React.lazy
React.Suspense
dynamic import
// 实现的效果与React支持内容保持一致
import React, {Suspese, lazy} from 'react'
const About= lazy(() => { import('../About') });
class App extends React.Component {
render() {
/**
* 1. 使用 React.Lazy 和 import() 来引入组件
* 2. 使用<React.Suspense></React.Suspense>来做异步组件的父组件,并使用 fallback 来实现组件未加载完成时展示信息
* 3. fallback 可以传入html,也可以自行封装一个统一的提示组件
*/
return (
<div>
<Suspense fallback={<Loading />}>
<About />
</Suspense>
</div>
)
}
}
export default ReactComp;
手写lazy susppend
// comp Suspense
import React from 'react'
class Suspense extends React.PureComponent {
/**
* isRender 异步组件是否就绪,可以渲染
/
state = {
isRender: true
}
componentDidCatch(e) {
this.setState({ isRender: false })
e.promise.then(() => {
/ 数据请求后,渲染真实组件 */
this.setState({ isRender: true })
})
}
render() {
const { fallback, children } = this.props
const { isRender } = this.state
return isRender ? children : fallback
}
}
export default Suspense
// comp lazy
import React, { useEffect } from 'react'
export function lazy(fn) {
const fetcher = {
status: 'pending',
result: null,
promise: null,
}
return function MyComponent() {
const getDataPromise = fn()
fetcher.promise = getDataPromise
getDataPromise.then(res => {
fetcher.status = 'resolved'
fetcher.result = res.default
})
useEffect(() => {
if (fetcher.status === 'pending') {
throw fetcher
}
}, [])
if (fetcher.status === 'resolved') {
return fetcher.result
}
return null
}
}