关于hooks,你该知道的

关于hooks,你该知道的

 关于hooks使用,你该知道的,可以看看

首先,还是拿第一个hook例子来说。


const count=useCounter()
//别的地方调用了这个hook
function useCounter(){
console.log('调用了count')
// 这里是每次都打印出来的
const [count,setCount]=useState(0)
// useEffect(()=>{
//     setInterval(() => {
//         console.log('useCounter',count)
//         setCount(count + 1);
//     }, 1000);      
// },[])
// 这里的count取的是初始执行这个useEffect时候的值,
// 也就是0,所以每次加计算的都是从0开始,
// 所以这里的值应该会一直是0
// useEffect(()=>{
//     let id=setInterval(() => {
//         console.log('useCounter',count)
//         setCount(count + 1);
//     }, 1000);
// },[count])
// 每次重新执行,setInterval都是会重新定义,
// 所以需要一个全局变量来保存这个东西,比如用一个ref来保存,
// 或者使用函数模式设置count
    useEffect(()=>{
let id=setInterval(() => {
console.log('useCounter',count)
            setCount(c=>c+1);
        }, 1000);
// 不需要传入依赖,只执行一次,setInterval不会重置,
// setCount的函数设置方式,可以实现我们的效果
    },[])
// 或者全局挂在一个ref上面
return [count]
}

可以看出,hook的每一次渲染都会走到hook里面。

  • 这个里面好像是因为count值会变化,外面引用到了,所以每次都会重新渲染useCounter这个函数。

  • 但是这个里面的useEffect其实只会调用一次。

每一次渲染都有自己的事件处理函数,包括传入的参数

  • 我们的组件函数每次渲染都会被调用,每次调用的count都是常量,并且他被赋予了当前渲染中的状态值,所以才会有如果函数依赖外面的参数,就要把参数传入到第二个参数里面,当参数发生变化就让这个函数重新创建一遍。

  • 每一次渲染都有一个“新版本”的handleClick,每一个版本的handleClick都记住了自己的event。

  • vue更像是把所有数据都存在一个地方,每次用到参数的时候都去那里找当前最新的,这个函数一旦定义好就不变了。

  • 在任意一次的渲染中,props和state始终是不变的,props和state在不同的渲染中是相互独立的,所以使用到他们的任意值也都是相互独立的,他们都属于一次特定的渲染。即使事件处理中的异步函数调用“看到”的也是这次渲染中的值。也有可能不是最新的值。

effect:每次渲染都有他自己的effects

  • 并不是count值在“不变”的effect中发生了改变,而是effect函数本身在每一次渲染中都不同,如果effect依赖于外部的count值,那么每一个版本的effect应该都是根据最新的count来创建的。

  • 虽然effect是一个,但是每次渲染都是一个不同的函数,并且每个effect函数得到的props,或者state都来自属于他的那次特定渲染。

  • effect渲染的时间。

function Counter() {
  // ...
  useEffect(
    // Effect function from first render
    () => {
      document.title = `You clicked ${0} times`;
    }
  );
  // ...
}

// 渲染状态为 0时候的UI。

// 需要渲染的内容: <p>You clicked 0 times</p>。
// 渲染完了之后调用这个
// () => { document.title = 'You clicked 0 times' }。
// 开始更新UI,给DOM添加一些东西。
// 绘制到屏幕上。
// 运行给我的effect

// 运行 () => { document.title = 'You clicked 0 times' }。

// 初次渲染
function Counter() {
  // ...
  useEffect(
    // Effect function from second render
    () => {
      document.title = `You clicked ${1} times`;
    }
  );
  // ...
}

// React: 渲染状态为 1时候的UI。

// 需要渲染的内容: <p>You clicked 1 times</p>。
// 渲染完了之后调用
// effect:() => { document.title = 'You clicked 1 times' }。
// 开始更新UI,修改了DOM。
// 更改绘制到屏幕上了。
// 运行渲染effect

// 运行 () => { document.title = 'You clicked 1 times' }。
function Counter() {
  // ...
  useEffect(
    // Effect function from third render
    () => {
      document.title = `You clicked ${2} times`;
    }
  );
  // ..
}

1 .所有effects都会在渲染之后依次执行,概念上他只是组件输出的一部分,并且可以看到某次特定渲染的props和state。
2 .和这个不同的是之前的this.state.count,这种模式,只是会获取到最新的数据。

effect清理上次的函数

// 错误的认识:
// 假设第一次渲染的时候props是{id: 10},
// 第二次渲染的时候是{id: 20}

// 清除了 {id: 10}的effect。
// 渲染{id: 20}的UI。
// 运行{id: 20}的effect

// 正确的
// 渲染{id: 20}的UI。
// 屏幕上可以看到{id: 20}的UI。
// 清除{id: 10}的effect。
// 运行{id: 20}的effect。
// effect的清除并不会读取最新的props,
// 他只能读取到定义他的那次渲染的props值
  • react会根据我们当前的props,state同步到DOM。

  • useEffect使你能够根据props和state同步react tree之外的东西。

  • react并不能区分effects的不同,所以为了避免不重复的调用,可以给useEffect一个依赖数组参数。


useEffect(() => {
    document.title = 'Hello, ' + name;
  }, [name]); // Our deps
// 保证只使用了渲染中的name

// 如果当前渲染中的这些依赖项和上一次运行这个effect的时候值一样,
// 因为没有什么需要同步React会自动跳过这次effect:
  • 即使依赖数组中只有一个值在两次渲染中不同,也不会跳过effect的运行,会同步所有

移除依赖的常见方式

1. 让Effect自给自足,但是这种使用场景及其优先。放弃了外面修改state的权力。

useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(id);
}, [count]);

useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);
    return () => clearInterval(id);
}, []);

2. 使用reducer,解耦行为和操作数据的两个过程。是当你dispatch的时候,React只是记住了action - 它会在下一次渲染中再次调用reducer。在那个时候,新的props就可以被访问到,而且reducer调用也不是在effect里。

这就是为什么我倾向认为useReducer是Hooks的“作弊模式”。它可以把更新逻辑和描述发生了什么分开。结果是,这可以帮助我移除不必需的依赖,避免不必要的effect调用。

3. 把函数移到effects里面

function SearchResults() {
  const [query, setQuery] = useState('react');
  useEffect(() => {
    function getFetchUrl() {
      return 'https://hn.algolia.com/api/v1/search?query=' + query;
    }
    // 这个effect依赖的函数1
    async function fetchData() {
      const result = await axios(getFetchUrl());
        setData(result.data);
    }
    // 这个effect以来的函数2
    fetchData();
  }, [query]); // OK
  // ...
}

4 .实在不能放到effects里面的情况


// 只执行一次的useEffect
function SearchResults() {
  function getFetchUrl(query) {
    return 'https://hn.algolia.com/api/v1/search?query=' + query;
  }

  useEffect(() => {
    const url = getFetchUrl('react');
    ...
  }, []);

// 没使用依赖,只会在初始化的时候执行一次,
  useEffect(() => {
    const url = getFetchUrl('redux');
    ...
  }, []);
  ...
}

// 函数作为依赖,会怎样呢?
function SearchResults() {
  function getFetchUrl(query) {
    return 'https://hn.algolia.com/api/v1/search?query=' + query;
  }

  useEffect(() => {
    const url = getFetchUrl('react');
  }, [getFetchUrl]);

  useEffect(() => {
    const url = getFetchUrl('redux');
  }, [getFetchUrl]);
}
//每次渲染其实函数都是新的,所以这个依赖完全是废的,
// 其实还是每次刷新都触发effect执行,也就是请求数据
// 解决方法1 :如果一个函数没有使用组件内的任何值,
// 就把他提到组件外面定义,然后就可以*的在effect里面使用
function getFetchUrl(query) {
  return 'https://hn.algolia.com/api/v1/search?query=' + query;
}

function SearchResults() {
  useEffect(() => {
    const url = getFetchUrl('react');
  }, []);

  useEffect(() => {
    const url = getFetchUrl('redux');
  }, []);
  // 此时不需要再设为依赖,因为他不在渲染范围内,
  // 不会被数据流影响,不会因为props,state而改变
}

//解决方法2:包装为useCallck hook
function SearchResults() {
  const getFetchUrl = useCallback((query) => {
    return 'https://hn.algolia.com/api/v1/search?query=' + query;
  }, []);

  useEffect(() => {
    const url = getFetchUrl('react');
  }, [getFetchUrl]);

  useEffect(() => {
    const url = getFetchUrl('redux');
  }, [getFetchUrl]);
  // 本质上是给函数添加了一层依赖检查,只有函数依赖的参数发生变化,
  // 函数才会变化,而不是仅仅简单的去掉函数的依赖,
  // 那么effect依赖数组的检查是不是也可以这么来??
}

往期阅读

1、React Hooks介绍

2、React Hooks概览

3、使用 State Hook

4、使用 Effect Hook

5、Hooks使用规则

6、自定义 Hook

上一篇:【20211222】CmsWing代码分析 - src/controller/extend/controller.js(三)


下一篇:Hooks中的useState