Hooks笔记 01 React 新特性

176 阅读4分钟

一 React 新特性一览

01 contextType 用来替代 <BatteryContext.Consumer>

class Leaf extends Component {
  static contextType = BatteryContext;

  render() {
    const battery = this.context;
    return (
      <h1>Battery: {battery}</h1>
    );
  }
}

02 Lazy 与 Suspense

延迟加载

  • webpack - code splitting 代码拆分
  • import

react lazy封装了组件导入行为

import React, { Component, lazy, Suspense } from "react";

const About = lazy(() => import(/* webpackChunkName: "about" */ "./About"));

// ErrorBoundary  错误边界
// componentDidCatch or getDerivedStateFromError

class App extends Component {
  state = {
    hasError: false,
  };

  static getDerivedStateFromError() {
    return {
      hasError: true,
    };
  }

  render() {
    if (this.state.hasError) {
      return <div>error</div>;
    }
    return (
      <div>
        <Suspense fallback={<div>loading</div>}>
          <About />
        </Suspense>
      </div>
    );
  }
}

export default App;

03 Memo 指定组件渲染

PureComponent可优化浅比较渲染,但深层数据变化无法识别,容易触发视图不更新bug

class Foo extends PureComponent {
  render(){
    console.log('Foo render');
    return null;
  }
}

修复视图不更新bug

callback = () => {}
//...
<Foo person={person} cb={this.callback} />

每次都向Foo传入新的cb函数,从而触发Foo更新

function形态组件

const Foo = memo(function Foo(props) {
  console.log("Foo render");
  return <div>{props.person.age}</div>;
});

二 颠覆性新特性Hooks

优化了类组件的三大问题

  • 函数组件无this问题
  • 自定义Hook方法复用状态逻辑
  • 副作用的关注点分离

01 State Hooks

一个类组件

class App extends Component {
  state = {
    count: 0,
  };
  render() {
    const { count } = this.state;

    return (
      <button
        type="button"
        onClick={() => {
          this.setState({ count: count + 1 });
        }}
      >
        Click ({count})
      </button>
    );
  }
}

一个Hook组件

function App() {
  const [count, setCount] = useState(0);
  return (
    <button
      type="button"
      onClick={() => {
        setCount(count + 1);
      }}
    >
      Click ({count})
    </button>
  );
}

useState严格要求调用,开发中易出错

可使用eslint-plugin-react-hooks 来避免此问题发生

  1. 安装npm i eslint-plugin-react-hooks -D
  2. 配置eslint
 "eslintConfig": {
    "extends": "react-app",
    "plugins": [
      "react-hooks"
    ],
    "rules": {
      "react-hooks/rules-of-hooks": "error"
    }
  },
  1. 重启

性能优化

function App(props) {
  // const defaultCount = props.defaultCount || 0;
  
  const [count, setCount] = useState(() => {
    // 优化props数据性能,此函数只运行一次
    console.log('initial count');
    return props.defaultCount || 0;
  });

02 Effect Hooks

类组件副作用时机

  • Mount 之后
  • Update 之后
  • Unmount 之前

现在使用useEffect,useEffect会在render之后调用

一个类组件生命周期

componentDidMount(){
    document.title = this.state.count;
    window.addEventListener('resize', this.onResize, false);
  }

  componentWillUnmount(){
    window.removeEventListener('resize', this.onResize, false);
  }

  componentDiUpdate(){
    document.title = this.state.count;
  }

一个Hook Effect

  useEffect(() => {
    document.title = count;
  });

  useEffect(() => {
    window.addEventListener("resize", onResize, false);

    // 重渲染或卸载时调用
    return () => {
      window.removeEventListener("resize", onResize, false);
    };
  }, []);

第二个参数 [],每项都不变才会阻止useEffect执行。

  1. 如果不传数组,意味着每次渲染都执行useEffect
  2. 传空数组,useEffect只在第一次渲染执行一次
  3. 下列只在count变化后才会执行useEffect
useEffect(()=>{
	console.log('count:', count);
}, [count])

03 Context Hooks

一段原始Consumer

const CountContext = createContext();

class Foo extends Component {
  render() {
    return (
      <CountContext.Consumer>
        {(count) => <h1>{count}</h1>}
      </CountContext.Consumer>
    );
  }
}

<CountContext.Provider value={count}>
        <Foo />
</CountContext.Provider>

一段 contextType 版 Consumer

const CountContext = createContext();

class Bar extends Component {
  static contextType = CountContext;
  render() {
    const count = this.context;
    return <h1>{count}</h1>;
  }
}

<CountContext.Provider value={count}>
        <Bar />
</CountContext.Provider>

一段Hook 版 useContext

const CountContext = createContext();

function Counter() {
  const count = useContext(CountContext);
  return <h1>{count}</h1>;
}

<CountContext.Provider value={count}>
        <Counter />
</CountContext.Provider>

04 Memo&Callback Hooks

  // 渲染间完成, 第二个参数规则同useEffect
  const double = useMemo(() => {
    return count * 2;
  }, [count]);
  
  // 第二个参数空数组,只渲染一次,以此优化onClick性能
  const onClick = useMemo(()=>{
  	return () => {
    	console.log('Click')
    }
  },[])
  
  // 等价 useMemo返回函数情况  useMemo(() => fn)
  const onClick = useCallback(()=>{
  	console.log('Click')
  },[])
 

useCallback优化

const [ClickCount, setClickCount] = useState(1);

const onClick = useCallback(()=>{
  	console.log('Click')
    setClickCount(ClickCount + 1);
  },[ClickCount])
  
  
// 可省略数组中更新依赖,等价上面
const onClick = useCallback(()=>{
  	console.log('Click')
    setClickCount((ClickCount) => ClickCount + 1);
  },[])

05 Ref Hooks

使用场景一 调用类组件内的方法 或 DOM

const onClick = useCallback(() => {
    // 通过 ref 调用类组件内的方法
    counterRef.current.speak();
  }, [counterRef]);

使用场景二 避免it每次渲染重赋值(需要访问上次渲染数据)

  const it = useRef();

  // init启动setInterval 每秒+1
  useEffect(() => {
    it.current = setInterval(()=>{
      setCount(Count => Count + 1);
    }, 1000)
  }, [])

  // 每次渲染都查看count值
  useEffect(()=>{
    if(count >= 10){
      clearInterval(it.current)
    }
  })

06 自定义Hooks 状态逻辑的复用

使用自定义Hooks 抽离count逻辑

function useCount(defalultCount) {
  const [count, setCount] = useState(defalultCount);
  const it = useRef();

  useEffect(() => {
    it.current = setInterval(() => {
      setCount((Count) => Count + 1);
    }, 1000);
  }, []);

  useEffect(() => {
    if (count >= 10) {
      clearInterval(it.current);
    }
  });

  return [count, setCount];
}

function App(props) {
  const [count, setCount] = useCount(0);
  // ...  
}

另一个例子

function useSize() {
  const [Size, setSize] = useState({
    width: document.documentElement.clientWidth,
    height: document.documentElement.clientHeight,
  });

  // 每次resize都传递新的函数, 导致性能问题
  // 通过使用useCallback 缓存箭头函数,避免不必要的性能消耗
  const onResize = useCallback(() => {
    setSize({
      width: document.documentElement.clientWidth,
      height: document.documentElement.clientHeight,
    });
  }, []);

  useEffect(() => {
    window.addEventListener("resize", onResize, false);

    return () => {
      window.removeEventListener("resize", onResize, false);
    };
  }, []);

  return Size;
}

function App(props) {
	const size = useSize();
    return (<div>{size.width}*{size.height}</div>)
}

function Count(props) {
	const size = useSize();
    return (<div>{size.width}*{size.height}</div>)
}

07 Hooks的使用法则

一 只在最顶层使用 Hook

不要在循环,条件或嵌套函数中调用 Hook

二 只在 React 函数中调用 Hook

不要在普通的 JavaScript 函数中调用 Hook

08 Hooks的常见问题

一 生命周期对应

根据props 变化更新state

class Counter extends Component {
  state = {
    overflow: false,
  };
	
  static getDerivedStateFromProps(props, state){
    if(props.count > 10){
      return {
        overflow: true
      }
    }
  }
}

// 等价函数写法
function Counter(props){
  const [overflow, setOverflow] = useState(false);

  if(props.count > 10){
    setOverflow(true);
  }
}
function App(){
  useEffect(()=>{
    // componentDidMount
    return ()=>{
      // componentWillUnmount 
    }
  }, []);

  let renderCounter = useRef(0);
  renderCounter.current++;

  useEffect(()=>{
    if(renderCounter > 1){
      // componentDidUpdate
    }
  })
}

二 获取历史props或state

function Counter() {
  const [count, setCount] = useState(0);

  const prevCountRef = useRef();

  useEffect(() => {
    prevCountRef.current = count;
  });

  const prevCount = prevCountRef.current;

  return (
    <h1>
      Now: {count}, before: {prevCount}
    </h1>
  );
}

三 强制更新Hooks组件

function Counter() {
  const [Updater, setUpdater] = useState(0);

  function forceUpdate() {
    setUpdater((Updater) => Updater + 1);
  }
  
}