react 第二节

70 阅读4分钟

HOC :高阶组件  纯函数 入参是组件出参也是组件

使用HOC的原因:

  1. 抽取重复代码,实现组件复用:相同功能组件复用

  2. 条件渲染,控制组件的渲染逻辑(渲染劫持):权限控制。

  3. 捕获/劫持被处理组件的生命周期,常见场景:组件渲染性能追踪、日志打点。

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
  }
}