想阅读更多优质文章请猛戳 GitHub 博客, 一年百来篇优质文章等着你!
- React 新特性讲解及实例(一)
- React 新特性 Hooks 讲解及实例(二)
- React 新特性 Hooks 讲解及实例(三)
使用 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
的操作。
运行结果:
显示当 count
为 5
的时候并没有停止,这是为什么呢?
因为在 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
返回的结果赋值给 it
的 current
属性。
运行结果:
你应该熟悉 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:详见官网.
-
render
:这是函数组件体本身。 -
componentDidMount
,componentDidUpdate
,componentWillUnmount
:useEffect Hook 可以表达所有这些 (包括 不那么 常见 的场景) 的组合。 -
componentDidCatch
andgetDerivedStateFromError
:目前还没有这些方法的 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/qq44924588…
我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!
关注公众号,后台回复 福利,即可看到福利,你懂的。