Skip to content

hooks 的知识点 #8

Description

@ASkyBig

hooks 诞生的原因

类组件不不足

1 、状态逻辑复用困难

  • 缺少复用机制
  • 渲染属性和高阶组件导致的层级冗余

2、 复杂难以维护

  • 生命周期函数混合不相干逻辑
  • 相干逻辑分散在不同生命周期

3、this 指向麻烦

  • 内联函数过度创建新句柄
  • 类成员函数不能保证this

hooks 优势

  • 函数组件无 this 问题
  • 自定义 hook 逻辑复用方便
  • 副作用关注点分离

与class的对比

对于一个按钮点击count加一,分别用 classfunction 形式写一遍:

class 形式 codesandbox

import React from "react";

export default class App extends React.Component {
  state = {
    count: 0
  };
  render() {
    const { count } = this.state;
    return (
      <button onClick={() => this.setState({ count: count + 1 })}>
        Count{count}
      </button>
    );
  }
}

function 形式codesandbox

import React, { useState } from "react";

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

可以看到,function 形式的组件比 class 形式的组件简洁很多。

useState

  • 如果 count 为0, setCount(0) 不会触发重新渲染 codesandbox
import React, { useState } from "react";

export default function App() {
  const [count, setCount] = useState(0);

  return <button onClick={() => setCount(0)}>Click({count})</button>;
}
  • 如果从 props 里面取值复杂,将 useState 参数作为函数返回值,只会执行一次codesandbox
import React, { useState } from "react";

export default function App(props) {
  const [count, setCount] = useState(() => {
    console.log("get props");
    return props.defaultCount || 0;
  });

  return <button onClick={() => setCount(count + 1)}>Count{count}</button>;
}

useEffect

import React from "react";
class Count extends React.Component {
  state = {
    count: 0,
    size: {
      width: document.documentElement.clientWidth,
      height: document.documentElement.clientHeight
    }
  };
  onResize = () => {
    this.setState({
      size: {
        width: document.documentElement.clientWidth,
        height: document.documentElement.clientHeight
      }
    });
  };
  // resize 的监听和解绑分散在不同生命周期
  componentDidMount() {
    // title 和 resize 耦合在一起
    document.title = this.state.count;
    window.addEventListener("resize", this.onResize, false);
  }
  componentWillUnmount() {
    console.log("remove listener");
    window.removeEventListener("resize", this.onResize, false);
  }
  componentDidUpdate() {
    document.title = this.state.count;
  }
  render() {
    console.log("render");
    const { count, size } = this.state;
    return (
      <button onClick={() => this.setState({ count: count + 1 })}>
        Count({count}) Size: {size.width}*{size.height}
      </button>
    );
  }
}
export default class App extends React.Component {
  state = {
    id: 1
  };

  render() {
    const { id } = this.state;
    return (
      <div>
        {id & 1 ? <button>111</button> : <Count />}
        <button onClick={() => this.setState({ id: id + 1 })}>appClick</button>
      </div>
    );
  }
}

import React, { useState, useEffect } from "react";

export default function App() {
  const [id, setId] = useState(0);
  return (
    <div>
      {id & 1 ? <button>111</button> : <Count />}
      <button onClick={() => setId(id + 1)}>app click</button>
    </div>
  );
}

function Count() {
  console.log("render");
  const [count, setCount] = useState(0);
  const [size, setSize] = useState({
    width: document.documentElement.clientWidth,
    height: document.documentElement.clientHeight
  });

  const onResize = () => {
    setSize({
      width: document.documentElement.clientWidth,
      height: document.documentElement.clientHeight
    });
  };
// 关注点分离,title 和 resize 各自处理各自逻辑
  useEffect(() => {
    console.log("title");
    document.title = count;
  });

  useEffect(() => {
    console.log("resize");
    window.addEventListener("resize", onResize, false);
    return () => {
      console.log("remove listener");
      window.removeEventListener("resize", onResize, false);
    };
  }, []);

  return (
    <button onClick={() => setCount(count + 1)}>
      Count({count}) Size({size.width}* {size.height})
    </button>
  );
}

useEffect 的一个错误的示例 codesandbox,本意是想让 count 累加到10,然后自动停止。

import React, { useState, useEffect } from "react";

export default function App() {
  const [count, setCount] = useState(0);
  let timer;

  useEffect(() => {
    timer = setInterval(() => {
      console.log("setInterval", count);
      setCount(count + 1);
    }, 1000);
  }, []);

  useEffect(() => {
    if (count > 10) {
      clearInterval(timer);
    }
  });
  return <button onClick={() => setCount(count + 1)}>Count({count})</button>;
}

需要像下面这样:codesandbox

import React, { useState, useEffect } from "react";

export default function App() {
  const [count, setCount] = useState(0);
  let timer;

  useEffect(() => {
    timer = setInterval(() => {
      console.log("setInterval", count);
      setCount(count => count + 1);
    }, 1000);
  }, []);

  useEffect(() => {
    if (count > 10) {
      clearInterval(timer);
    }
  });
  return <button onClick={() => setCount(count + 1)}>Count({count})</button>;
}

还有一个问题,如果上面代码中

 useEffect(() => {
    timer = setInterval(() => {
      console.log("setInterval", count);
      setCount(count => count + 1);
    }, 1000);
  }, []);

变成下面这样,会导致什么问题呢?

 useEffect(() => {
    timer = setInterval(() => {
      console.log("setInterval", count);
      setCount( count + 1);
    }, 1000);
  }, [count]);

useMemo

目前理解不是很深刻

有记忆功能,点击 age 的时候不会改变 count,所以就不会输出 calc countcodesandbox

import React, { useState, useMemo } from "react";

const Counter = function(props) {
  console.log("Counter");
  return <div>{props.count}</div>;
};
export default function App() {
  const [count, setCount] = useState(0);
  const [age, setAge] = useState(20);

  const dobule = useMemo(() => {
    console.log("calc count");
    if (count > 3) {
      return count * 2;
    }
  }, [count]);

  return (
    <div>
      <Counter count={dobule} />
      <button onClick={() => setAge(age + 1)}>age: {age}</button>
      <button onClick={() => setCount(count + 1)}>
        Count({count}) double({dobule})
      </button>
    </div>
  );
}

不过有一个问题, Counter 组件会一直渲染,其实该组件只依赖于 count,不应该点击 age 时重新渲染它。可以用 memo 包裹一下就好了,这样你点击 age 就不会重渲染 Countercodesandbox

import React, { useState, useMemo, memo } from "react";

const Counter = memo(props => {
  console.log("Counter");
  return <div>Counter({props.count})</div>;
});

export default function App() {
  const [count, setCount] = useState(0);
  const [age, setAge] = useState(20);

  const dobule = useMemo(() => {
    console.log("calc count");
    if (count > 3) {
      return count * 2;
    }
  }, [count]);

  return (
    <div>
      <Counter count={dobule} />
      <button onClick={() => setAge(age + 1)}>age: {age}</button>
      <button onClick={() => setCount(count + 1)}>
        Count({count}) double({dobule})
      </button>
    </div>
  );
}

useRef

  • 获取dom元素
  • 不同生命周期需要共享的数据

自定义hook

把上面的例子用自定义hook重写:codesandbox

import React, { useState, useEffect, useRef } from "react";

function useCount1(count) {
  return <h1>{count}</h1>
}

function useCount(defaultCount) {
  const [count, setCount] = useState(defaultCount);
  let timer = useRef();

  useEffect(() => {
    timer.current = setInterval(() => {
      console.log("setInterval", count);
      setCount(count => count + 1);
    }, 1000);
  }, []);

  useEffect(() => {
    if (count >= 10) {
      clearInterval(timer.current);
    }
  });
  return [count, setCount];
}
export default function App() {
  const [count, setCount] = useCount(0);
  const Count1 = useCount1(count)
  return (
    <div>
      {Count1}
      <button onClick={() => setCount(count + 1)}>Count({count})</button>;
    </div>
  )
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    hooksreact hooksreactsomething about react

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions