Hooks API 是 React 16.7.0-alpha.0 版本推出的 API,要注意的是只有这个版本才有,别的版本都没有,如果你在 16.7.0 正式版是没有的!我就是被坑了。
如果你用 create-react-app 来创建应用的,现在的版本会是 16.7.0,一定要改成 16.7.0-alpha.0 才能使用 Hooks API
useState
useState API 本质了为了使得函数式组件也能有自身状态而推出的 API。
以前的函数组件
假如我们要做一个简单的计数器,如果用以前的函数式组件是不行的,因为函数式组件是没有自身状态的。比如:
function App()l {
let count = 0
let add = function() {}
return (
<div>
<div>{count}</div>
<div><button onClick={add}>+1</button></div>
</div>
)
}
你可能说 count
不行么?因为每次 render 的时候这个函数就会被执行一次,那么就会执行 let count = 0
,所以每次都会重置了 count
,没用。
解决方法是用 class 组件,在 this.state
对象里放入 count
:
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
}
add = () => {
this.setState({
count: this.state + 1
})
}
render() {
return (
<div>
<div>App: {count}</div>
<div>
<button onClick={add}>+1</button>
</div>
</div>
)
}
}
烦不烦?写那么多个单词。所以 Hooks API 出现了,它可以给函数式组件拥有自身状态。
使用 useState
useState 的作用就是创建一个状态,可以是对象也可以是别的类型,还会创建一个修改这个状态的函数。上面的例子用新 API 可以写成这样:
function App() {
// 状态和修改状态的函数
const [count, setCount] = useState(0)
// Handler
const add = () => {
setCount(count + 1)
}
return (
<div>
<div>App: {count}</div>
<div>
<button onClick={add}>+1</button>
</div>
</div>
)
}
是不是感觉清爽很多了?没有什么 constructor, super, render 那么多东西,现在只需要我好好写一个函数就行了。
注意
- 不能在函数式组件外面使用 useState,这个很好理解,毕竟定义的就是自己的状态当然在组件自己里使用
- 数组里的名字可以改,可以是
const [x, y] = useState(0)
,你自己清楚 x, y 代表什么就好了
useEffects
这个 API 就有点难理解了,Effect 的翻译是影响,副作用。这里的 Effects,相当于你使用了别人的东西。
什么是 Effect
这里的 Effect 分两种:一种叫 Effects without Cleanup,就是说你拿别人的东西吃,吃完这个东西就没了,不用再去清理,因为东西就在你肚子里呀。另一种叫 Effects with Cleanup,比如你拿别人的书看,看完了还得将书要么扔了,要么还给别人,总之这本书你得处理。
那么 React Effects 有哪些呢?简单的如:DOM 修改,网络请求,订阅事件,这些都是使用了组件之外的资源,所以每一次的这些行为就是一个 Effect。
那为毛要搞一个 useEffects 呢?下面就分两种 Effects 情况来说说吧,同时讲下怎么使用 useEffects。
Effects with Cleanup
想像一个场景,每次更新 count 之后都要设置 document.title = count
,你会怎么做。哎,这个简单,在上面的 add()
函数里直接改不就好了?但是假如这个 count
作为共享数据可以被所有组件修改的,而且有 minus()
, divide()
, multiple()
等方法去改这个值,那就要重复很多次 document.title = count
。
这时候我们可以总结出不管怎样去修改,反正就是修改后就要执行 document.title = count
代码嘛。即然是 count
值每次都修改就会导致这个组件重新渲染,那么那我在生命钩子componentDidUpdate
和 componentDidMount
里去改 title不就好了?如:
class App extends React.Component {
constructor(props) {
super(props)
this.state = { count: 0 }
}
componentDidMount() {
document.title = this.state.count
}
componentDidUpdate() {
document.title = this.state.count
}
render() {
return (...)
}
}
还是那个问题:烦不烦啊,还要我写两次。这时候 React 就推出了 useEffect,即每次渲染(包括 mounted 和 updated)后,即就是说每次执行 render 后,会回调一个函数,这个函数执行你要做的事情。
可以将上面的改写成这样。
function App (props) {
useEffect(() => {
document.title = count
})
return (...)
}
代码变成6行,舒服~。再回来看看 Effect 的定义,使用组件外的东西,对呀我们使用了 document
对象,这不是组件里的呀。好像也不太需要清理什么东西,因为就设置一个值嘛。所以这就叫 Effects without Cleanup。
Effects with Cleanup
有 without 就有 with,什么时候要 Cleanup 呢?那肯定是无端端创建东西的时候嘛,像垃圾回收一样,如果创建的东西不再需要了,那就清掉呗。React 里的例子是添加监听事件喽。
比如在一个 MyButton 组件开始的时候我要默认添加一个事件中心的监听回调,然后这个组件销毁的时候回收这个回调函数。使用生命钩子会写成这样:
class MyButton extends React.Component {
constructor(props) {
super(props)
this.state = { isTriggered: false }
}
componentDidMount() {
EventHub.on('Trigger event', this.state.isTriggered, () => {
console.log('Do something')
})
}
componentWillUnmount() {
EventHub.removeListener('Trigger event', this.state.isTriggered, () => {
console..log('Listener is removed')
})
}
render() {
return ( ... )
}
}
我们使用了 EventHub,很明显这不是组件里的东西,是属性外面的,所以这也是个 Effect。
而这次使用 useEffect
就不太一样了,因为我们是要擦屁股的,在组件 unmount 的时候取消监听。而 useEffect
提供了一个很好理解的用法。
function MyButton(props) {
useEffect(() => {
EventHub.on('Trigger event', this.state.isTriggered, () => {
console.log('Do something')
})
return function cleanup() {
EventHub.removeListener('Trigger event', this.state.isTriggered, () => {
console.log('listener is removed')
})
}
})
return (...)
}
当组件 unmount 的时候就会去执行 cleanup 这个函数,是不是感觉一目了然?这里的函数名不是必须的,只是为了说明这里用来清理而已。
注意
- 代码简洁就不用说太多了,一看就知道谁好用了
- 而因为使用生命钩子是同步的,会阻塞页面的渲染,或者组件的销毁也会造成页面卡顿。而
setEffect
里面其实是异步进行的不会有这些影响 - 当然
setEffect
也不是全都是好的,比如每次 render 后都会用一个一模一样的函数去替换原来来的回调,以保持状态都是最新的
当然这只是简单的用法,更多的用法请看官方文档
网友评论