Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React 新特性 Hooks 讲解及实例(四) #75

Open
husky-dot opened this issue Jul 12, 2019 · 0 comments
Open

React 新特性 Hooks 讲解及实例(四) #75

husky-dot opened this issue Jul 12, 2019 · 0 comments

Comments

@husky-dot
Copy link
Owner

使用 Ref Hooks

类组件中使用 Ref 一般有:

  • String Ref
  • Callback Ref
  • CreateRef

上述在函数组件中没有办法使用它们,取而代之的是 useRef Hooks。

useRef 主要有两个使用场景:

  • 获取子组件或者 DOM 节点的句柄
  • 渲染周期之间的共享数据的存储

大家可能会想到 state 也可跨越渲染周期保存,但是 state 的赋值会触发重渲染,但是 ref 不会,从这点看 ref 更像是类属性中的普通成员。

粟例说明一下:获取子组件或者 DOM 节点的句柄

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

本质上,useRef 就像是可以在其 .current 属性中保存一个可变值的“盒子”。

粟例说明一下:渲染周期之间的共享数据的存储

function App (props) {
  const [count, setCount] = useState(0);
  let it
  useEffect(() => {
    it = setInterval(() => {
      setCount(count => count + 1)
    }, 1000)
  } , [])

  useEffect(() => {
    if (count >= 5) {
      clearInterval(it)
    }
  })

  return (
    <div style={{padding:'100px'}}>
      <h1>{count}</h1>
    </div>
  )
}

上述使用 useEffect 声明两个副作用,第一个每隔一秒对 count 加 1,因为只需执行一次,所以每二个参为空数组。第二个 useEffect 判断 count 大于等于时,停止对 count 的操作。

运行结果:

显示当 count5 的时候并没有停止,这是为什么呢?

因为在 clearInterval, it 这个变量已经不是 setInterval 赋值时的那个了,每次 App 重渲染都会重置它。这时候就可以使用 useRef 来解决这个问题。

function App (props) {
  const [count, setCount] = useState(0);
  const it = useRef(null)
  
  useEffect(() => {
    it.current = setInterval(() => {
      setCount(count => count + 1)
    }, 1000)
  } , [])

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

  return (
    ...
  )
}

使用 useRef 来创建一个 it, 当 setInterval 返回的结果赋值给 itcurrent 属性。

运行结果:

你应该熟悉 ref 这一种访问 DOM 的主要方式。如果你将 ref 对象以 <div ref={myRef} /> 形式传入组件,则无论该节点如何改变,React 都会将 ref 对象的 .current 属性设置为相应的 DOM 节点。

然而,useRef()ref 属性更有用**。它可以很方便地保存任何可变值**,其类似于在 class 中使用实例字段的方式。

这是因为它创建的是一个普通 Javascript 对象。而 useRef() 和自建一个 {current: ...} 对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象。

请记住,当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。

自定义 Hook

前面三篇,我们讲到优化类组件的三大问题:

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

对于组件的复用状态没怎么说明,现在使用自定义 Hook 来说明一下。

首先我们把上面的例子用到 count 的逻辑的用自定义 Hook 封装起来:

function useCount(defaultCount) {
  const [count, setCount] = useState(defaultCount);
  const it = useRef()
      
  useEffect(() => {
    it.current = setInterval(() => {
      setCount(count => count + 1)
    }, 1000)
  } , [])

  useEffect(() => {
    if (count >= 5) {
      clearInterval(it.current)
    }
  })
  
  return [count, setCount]
}


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

  return (
    <div style={{padding: '100px'}}>
      <h1>{count}</h1> 
    </div>
  )
}

运行效果:

可以看出运行效果跟上面是一样的。

定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook。我们在函数自定义写法上似乎和编写函数组件没有区别,确实自定义组件与函数组件的最大区别就是输入与输出的区别。

再来一个特别的 Hook 加深一下映像。在上述代码不变的条件下,我们在加一个自定义 Hook 内容如下:

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

在 App 组件调用:

function App (props) {
  const [count, setCount] = useCount(0);
  const Counter = useCounter(count)
  return (
    <div style={{padding: '100px'}}>
      {Counter}
    </div>
  )
}

运行效果:

我们自定义 useCounter Hook返回的是一个 JSX,运行效果是一样的,所以 Hook 是可以返回 JSX 来参与渲染的,更说明 Hook 与函数组件的相似性。

使用 Hook 的法则

只在最顶层使用 Hook

不要在循环,条件或嵌套中调用 Hook,确保总是在你的 React 函数的最顶层调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这上 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确。

只在 React 函数中调用 Hook

不要在普通的 JavaScript 函数中调用 Hook, 你可以:

  • 在 React 的函数组件中调用 Hook
  • 在自定义 Hook 中调用其它 Hook

Hooks 常见问题

以下主要说明几个典型的问题,当然这在官网上都有说明。

生命周期方法要如何对应到 Hook?

  • constructor:函数组件不需要构造函数。你可以通过调用 useState 来初始化 state。如果计算的代价比较昂贵,你可以传一个函数给 useState

  • getDerivedStateFromProps:改为 在渲染时 安排一次更新

  • shouldComponentUpdate:详见[官网][9].

  • render:这是函数组件体本身。

  • componentDidMount, componentDidUpdate, componentWillUnmount:useEffect Hook 可以表达所有这些(包括 不那么 常见 的场景)的组合。

  • componentDidCatch and getDerivedStateFromError:目前还没有这些方法的 Hook 等价写法,但很快会加上。

如何强制更新一个 Hooks 组件

如果前后两次的值相同,useState 和 useReducer Hook 都会放弃更新。原地修改 state 并调用 setState 不会引起重新渲染。

通常,你不应该在 React 中修改本地 state。然而,作为一条出路,你可以用一个增长的计数器来在 state 没变的时候依然强制一次重新渲染:

  const [ignored, forceUpdate] = useReducer(x => x + 1, 0);

  function handleClick() {
    forceUpdate();
  }

可能的话尽量避免这种模式。

交流

干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。

https://github.com/qq449245884/xiaozhi

我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!

关注公众号,后台回复福利,即可看到福利,你懂的。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant