API思考-InteractionManager

544 阅读4分钟

runAfterInteractions(2022-09-24)

React Web 版

下面这段代码,是一段典型的React Web版代码,主组件App里,有一个子组件Ball,当App里的一些交互完成后,Ball才可以开始做一些动作。换句话说,Ball里的一些行为,是依赖于App里的一些交互完成的。一个典型的场景,就是App里要发送一些API拿数据,当数据都拿到了,Ball才能开始做一些动作。这里简化了API的抓取,用setTimeout来替代,本质上都是放在useEffect里的一些副作用。执行效果可以参看下面的动画截图,当三个时间延迟都完成后,App里的finished这个state就由false变成了true,于是Ball就做了它要做的动作,alert一个message。

import React, { useEffect, useState } from 'react';
import {
  Alert,
  StyleSheet,
  View,
} from 'react-native';

const Ball = ({ onInteractionIsDone, finished }) => {
  useEffect(() => {
    if (finished) {
      onInteractionIsDone()
    }
  }, [finished])
  return <View style={[styles.ball]} />;
};

const App = () => {
  const [finished1, setFinished1] = useState(false)
  const [finished2, setFinished2] = useState(false)
  const [finished3, setFinished3] = useState(false)

  useEffect(() => {
    setTimeout(() => {
      setFinished1(true)
    }, 2000);
  }, []);

  useEffect(() => {
    setTimeout(() => {
      setFinished2(true)
    }, 1500);
  }, []);

  useEffect(() => {
    setTimeout(() => {
      setFinished3(true)
    }, 3000);
  }, []);

  return (
    <View style={styles.container}>
      <Ball onInteractionIsDone={() => Alert.alert('done by normal way')} 
      finished={finished1 && finished2 && finished3} />
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
  ball: {
    width: 100,
    height: 100,
    backgroundColor: 'salmon',
    borderRadius: 100,
  },
});

export default App;
web_interation.gif

React Native 单组件版

在React Native中,有一个在Web中无法使用的API - InteractionManager。其中最常用的,是runAfterInteractions方法。这个方法的意思是说:当用户的交互(包括API返回,timeout,动画)完成后,才开始调用作为参数传递给该方法的那个方法。说白了,就是传递一个回调函数给runAfterInteractions,等待交互完成后,runAfterInteractions会自动调用那个回调函数。这样一来,我就不需要主动通过state来控制Ball这个子组件了,把控制权,交给了InteractionManager,它是知道什么时候交互完成的。

听起来挺高大上的,但是以我浅薄的认知来看,这不就是用一个全局的事件监控跨组件来同步状态么?和jQuery里发一个全局事件,或者Rxjs里多播一条消息,是不是本质上都一样呢?

遵循着这一类设计范式共有的特点,首先要告诉InteractionManager,哪儿些东西,是需要它来监控的,这里用

const handle = InteractionManager.createInteractionHandle()

来创建。

同理,还需要在不需要监控的时候,告诉InteractionManager,不用再监控了。这里用

InteractionManager.clearInteractionHandle(handle)

来销毁。

详细代码请参考下面。这段代码里,除了用InteractionManager替代state同步状态外,还封装了所有的交互到一个自定义hook里,在Ball里使用这个hook,就等于把交互这件事情和Ball本身建立了联系。

import React, { useEffect, useState } from 'react';
import {
  Alert,
  StyleSheet,
  View,
  InteractionManager
} from 'react-native';

const useCustomInteraction = () => {
  useEffect(() => {
    const handle1 = InteractionManager.createInteractionHandle();
    setTimeout(
      () => InteractionManager.clearInteractionHandle(handle1),
      1500
    );
    return () => InteractionManager.clearInteractionHandle(handle1);
  }, []);

  useEffect(() => {
    const handle2 = InteractionManager.createInteractionHandle();
    setTimeout(
      () => InteractionManager.clearInteractionHandle(handle2),
      2000
    );
    return () => InteractionManager.clearInteractionHandle(handle2);
  }, []);

  useEffect(() => {
    const handle3 = InteractionManager.createInteractionHandle();
    setTimeout(
      () => InteractionManager.clearInteractionHandle(handle3),
      3000
    );
    return () => InteractionManager.clearInteractionHandle(handle3);
  }, []);
};

const Ball = ({ onInteractionIsDone }) => {
  useCustomInteraction()

  useEffect(() => {
    InteractionManager.runAfterInteractions(() => onInteractionIsDone());
  }, [])
  return <View style={[styles.ball]} />;
};

const App = () => {
  return (
    <View style={styles.container}>
      <Ball onInteractionIsDone={() => Alert.alert('done by interaction way')}/>
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
  ball: {
    width: 100,
    height: 100,
    backgroundColor: 'salmon',
    borderRadius: 100,
  },
});

export default App;

以下是演示效果

one_interaction.gif

感兴趣的话,可以把第一个用来销毁handle1的clearInteractionHandle注释掉,那样就会发现,alert永远不会弹出来了,因为InteractionManager一直没有等到交互完成。

React Native 多组件版

下面这个例子,在上一个例子的基础上,又多加了2个Components - Square和Triangle,这两个Components也和Ball一样,必须等到交互完成后,才开始做一些动作。此时就看出来我们封装了useCustomInteraction和使用InteractionManager的好处,复用性大大提高了,而且还不用每一个组件设置一个state来同步状态。

import React, { useEffect, useState } from 'react';
import {
  Alert,
  StyleSheet,
  View,
  InteractionManager
} from 'react-native';

const useCustomInteraction = () => {
  useEffect(() => {
    const handle1 = InteractionManager.createInteractionHandle();
    setTimeout(
      () => InteractionManager.clearInteractionHandle(handle1),
      1500
    );
    return () => InteractionManager.clearInteractionHandle(handle1);
  }, []);

  useEffect(() => {
    const handle2 = InteractionManager.createInteractionHandle();
    setTimeout(
      () => InteractionManager.clearInteractionHandle(handle2),
      2000
    );
    return () => InteractionManager.clearInteractionHandle(handle2);
  }, []);

  useEffect(() => {
    const handle3 = InteractionManager.createInteractionHandle();
    setTimeout(
      () => InteractionManager.clearInteractionHandle(handle3),
      3000
    );
    return () => InteractionManager.clearInteractionHandle(handle3);
  }, []);
};

const Ball = ({ onInteractionIsDone }) => {
  useCustomInteraction()

  useEffect(() => {
    InteractionManager.runAfterInteractions(() => onInteractionIsDone());
  }, [])
  return <View style={[styles.ball]} />;
};


const Square = ({ onInteractionIsDone }) => {
  useCustomInteraction()

  useEffect(() => {
    InteractionManager.runAfterInteractions(() => onInteractionIsDone());
  }, [])
  return <View style={[styles.square]} />;
};

const Triangle = ({ onInteractionIsDone }) => {
  useCustomInteraction()

  useEffect(() => {
    InteractionManager.runAfterInteractions(() => onInteractionIsDone());
  }, [])
  return <View style={[styles.triangle]} />;
};

const App = () => {
  return (
    <View style={styles.container}>
      <Ball onInteractionIsDone={() => Alert.alert('Ball done')}/>
      <Square onInteractionIsDone={() => Alert.alert('Square done')}/>
      <Triangle onInteractionIsDone={() => Alert.alert('Triangle done')}/>
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
  ball: {
    width: 100,
    height: 100,
    backgroundColor: 'red',
    borderRadius: 100,
  },
  square: {
    width: 100,
    height: 100,
    backgroundColor: 'green',
    borderRadius: 0,
  },
  triangle: {
    width: 0,
    height: 0,
    borderTopWidth: 50,
    borderTopColor: 'transparent',
    borderRightWidth: 100,
    borderRightColor: 'transparent',
    borderLeftWidth: 100,
    borderLeftColor: 'transparent',
    borderBottomWidth: 100,
    borderBottomColor: 'blue'
  },
});

export default App;
three_interaction.gif