在 React Native 中使用 Hooks

React官方在 2018 ReactConf 大会上宣布 React v16.7.0-alpha(内测) 将引入 Hooks。什么是Hooks,我们来了解一下。

什么是Hooks?

在平时开发过程中,我们一般都会遇到如下问题:

1. 难以重用和共享组件中的与状态相关的逻辑
2. 逻辑复杂的组件难以开发与维护,当我们的组件需要处理多个互不相关的 state 时,每个生命周期函数中可能会包含着各种互不相关的逻辑在里面。
3. 由于业务变动,函数组件不得不改为类组件等等。

上面说起来比较抽象,接下来我们以 键盘Api Keyboard 为例说明问题。


 
 
  1. export default class App extends Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = {
  5. isShowKeyboard: false
  6. }
  7. }
  8. static getDerivedStateFromProps() {
  9. this.keyboardDidShowListener = Keyboard.addListener(
  10. "keyboardDidShow",
  11. this.keyboardDidShowHandler
  12. );
  13. }
  14. keyboardDidShowHandler () {
  15. this.setState({
  16. isShowKeyboard: true
  17. });
  18. }
  19. componentWillUnmount() {
  20. this.keyboardDidShowListener.remove();
  21. }
  22. render() {
  23. return (
  24. <View style={styles.container}>
  25. <Text>
  26. 当前键盘状态: {this.state.isShowKeyboard}
  27. </Text>
  28. </View>
  29. );
  30. }
  31. }

上面的代码用例比较简单,使用 Keyboard 注册监听键盘的显示、隐藏状态。可以看到键盘事件的注册,注销,状态的render都放在了Component中,如果当前Component中涉及很多这样的逻辑,会造成当前Component职责非常重,并且状态数据不能共享,当在另一个Component中需要监听键盘事件时,需要重新编写或Copy重复代码,冗余非常严重,对于功能维护和扩展都不是一件好事。

Hooks的出现解决了上面的问题,它允许开发者定义函数组件,也可以使用类组件(class components)的 state 和 组件生命周期,而不需要在 mixin、 函数组件、HOC组件和 render props 之间来回切换。方便我们在业务中实现业务逻辑代码的分离和组件的复用。与使用 setState 相比,组件是没有状态的。来看看使用Hooks的方式:


 
 
  1. import { useState, useEffect } from 'react';
  2. import {
  3. Keyboard
  4. } from 'react-native';
  5. export default function keyboard() {
  6. const [keyboardStatus, setKeyboardStatus] = useState( false);
  7. Keyboard.addListener(
  8. "keyboardDidShow",
  9. this.keyboardDidShowHandler
  10. );
  11. useEffect( ()=> {
  12. return ()=> Keyboard.removeListener(
  13. "keyboardDidShow",
  14. ()=> setKeyboardStatus( true)
  15. );
  16. }, [ false]);
  17. return (
  18. <Text>
  19. 当前键盘状态:{keyboardStatus}
  20. </Text>
  21. )
  22. }

上述代码中将关于键盘的业务逻辑剥离到了函数中,称之为 函数组件。当我们在其他Component中使用时,只需要导入进来即可。在函数组件中,我们使用到了 useState、useEffect,它们作为Hooks中提供的Api,起到了什么作用呢?

Hooks Api

官方提供了 hooks 的三个关键的Api,分别是 State Hooks 、 Effect Hooks 、Context Hooks、 Custom Hooks(自定义hooks)。

useState

useState 这个方法可以为函数组件带来 local state,它接收一个用于初始 state 的值,返回一对变量


 
 
  1. // 等价于
  2. const keyboardStatus= useState( 0)[ 0];
  3. const setKeyboardStatus= useState( 0)[ 1];

理解起来比较简单,其实就是定义 state 状态值,以及修改该 state 状态值的行为函数。

useEffect

useEffect 可以简单的理解为替代如下生命周期:

componentDidMount、componentDidUpdate、componentWillUnmount 

useEffect 的代码既会在第一次初始化时(componentDidMount)执行,也会在后续每次触发 render 渲染时(componentDidUpdate)执行,返回值在组件注销时(componentWillUnmount)执行。结合上面的例子:


 
 
  1. useEffect( ()=> {
  2. // return 将会在组件注销时调用
  3. return ()=> Keyboard.removeListener(
  4. "keyboardDidShow",
  5. ()=> setKeyboardStatus( true)
  6. );
  7. }, [ false]);

useEffect 的第二个参数,作为性能优化的设置,决定是否执行里面的操作来避免一些不必要的性能损失。只要第二个参数数组中的成员的值没有改变,就会跳过此次执行。如果传入一个空数组 [ ],那么只会在组件 mount 和 unmount 时期执行。

Context Hooks

React 16.3 版本中发布了全新的Context API。目的为了解决子组件嵌套层级过深,父组件的属性难以传达的问题。使用方式不算复杂,首先要利用 Context API创建一个数据提供者(Provider)和数据消费者(Consumer)。(说到这里有点像Java多线程并发的例子)在提供者所在的地方存入数据,在消费者所在的地方取出数据。简单看下 Context 使用方式:

(1)创建上下文环境


 
 
  1. // 创建 Context
  2. import React from 'react';
  3. const DataContext = React.createContext({
  4. name: '',
  5. age: 23
  6. });
  7. export default DataContext;

(2)定义数据提供者 Provider


 
 
  1. export default class App extends Component {
  2. render() {
  3. return (
  4. <DataContext.Provider value={{ name: 'Songlcy', age: 27 }}>
  5. <CustomComponent />
  6. </DataContext.Provider>
  7. );
  8. }
  9. }

(3)定义数据消费者 Consumer


 
 
  1. export default class CustomComponent extends Component {
  2. render() {
  3. return (
  4. <DataContext.Consumer>
  5. {
  6. context => (
  7. <Text>
  8. 我的名字:{context.name}, 我的年龄:{context.age}
  9. </Text>
  10. )
  11. }
  12. </DataContext.Consumer>
  13. )
  14. }
  15. }

当组件嵌套层次很深的情况下,Context 的优势就会更为明显。

诶,醒醒!”.....   说了这么多,继续回到Hooks。上面代码中,从 Context — Provider — Consumer 获取到数据,整个取值过程还是比较繁琐的。当我们要从多个 Consumer 中取值的时候,还要进行函数嵌套,更加麻烦。

useContext 是对 Context API 的简化。来看看简化后的样子:

const { name, age } = useContext(DataContext);
 
 

我靠!这就完了?” 是的,取值过程就是这么简单,就是这么任性。再来10个 Consumer  又如何!

Custom Hooks

Custom Hooks 即自定义Hooks行为方式,本身并不是Api。核心概念就是将逻辑提取出来封装到函数中,具体实现就是通过一个函数封装跟状态数据(State)有关的逻辑,将这些逻辑从组件中抽取出来。在这个函数中我们可以使用其他的 Hooks,也可以单独进行测试。修改上面的例子:


 
 
  1. export default function useKeyboardStatus() {
  2. const [keyboardStatus, setKeyboardStatus] = useState( false);
  3. Keyboard.addListener(
  4. "keyboardDidShow",
  5. this.keyboardDidShowHandler
  6. );
  7. useEffect( ()=> {
  8. return ()=> Keyboard.removeListener(
  9. "keyboardDidShow",
  10. ()=> setKeyboardStatus( true)
  11. );
  12. },[]);
  13. return keyboardStatus;
  14. }

代码几乎相同,唯一区别是函数名称用了 use* 前缀,这里需要遵循一个约定,命名要用 use*

Hooks 工作原理

“神马?Hooks 其实就是一个数组!”

回忆下最初我们使用 useState 时的方式:

const [keyboardStatus, setKeyboardStatus] = useState(false);
 
 

其实从这句代码我们也能猜出大致的实现思想:

使用一个类似于 setter 的函数作为hook函数中的第二个数组项返回,而 setter 将控制由hook管理的状态(State),状态由第一个数组项返回。

我们可以理解成有两个数组,分别存放 state、setState对应的方法。

当useState()第一次运行时,将setter函数推送到setter数组,状态推送到state数组。每个setter都有一个对它的光标位置的引用,因此通过触发对任何setter的调用,它将改变状态数组中该位置的状态值。说白了就是有个索引,setter方法根据索引修改对应的状态数据值。来看看伪代码的实现方式:


 
 
  1. let state = []; // 存放state状态数据
  2. let setter = []; // 存放 setXXX方法
  3. let initial = true; // 是否是第一次运行
  4. let index = 0;
  5. useState(initVal) {
  6. if (initial) {
  7. state.push(initVal);
  8. setter.push(createSetter(index));
  9. initial = false;
  10. }
  11. const setter = setter[index];
  12. const value = state[index];
  13. index++;
  14. return [value, setter];
  15. }
  16. createSetter(index) {
  17. return function setterWithIndex(newVal) {
  18. state[index] = newVal;
  19. };
  20. }

具体的源码实现,感兴趣的大家可以去看看。不过不建议每步都弄懂,了解了实现思想就可以了。

总结

状态和相关的处理逻辑可以按照功能进行划分,不必散落在各个生命周期中,大大降低了开发和维护的难度。除了这几个hooks还有其他额外的hooks:Hooks API Reference

最后推荐两个个很牛逼的库:

react-use封装了各种 Hooks。

eslint-plugin-react-hooksHooks ESLint 插件

一个老外写的很不错的 React Native Hooks 文章:React Hooks Basics— Building a React Native App with React Hooks

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值