2022年06月13-17 开发小计

303 阅读3分钟

tag: [React Native]

InlineRequires

在查询资料时注意到了metro.config.json文件中的inlineRequires 配置项, 之前虽然有注意到, 但是没有去深入了解, 今天查阅了下, 发现它的作用是将 require变成内联(inline) 形式, 读者OS: "你这不是废话吗", 别着急, 然我们看几个例子: 请允许我直接抄代码注释:

// 转换之前
var Foo = require('foo');
// 转换之后
f(Foo);


// 转换之前
var Bar = require('foo').bar
g(Bar)
// 转换之后
g(require('foo').bar);


// 转换之前
const {Baz} = require('foo');
h(Baz);
// 转换之后
g(require('foo').Baz);

inlineRequires源码 上面的例子也来源于此. 对这个配置项不了解的小伙伴和笔者一样一脸懵逼, 没搞懂这个有什么用, 不过本着求真的精神笔者继续查询 inlineRequires相关的资料, 查到在 React Native 官方文档的一篇文章《RAM Bundles and Inline Requires》(英语不好的小伙伴也可以读这篇翻译好的文章). 它的主要用途是按需加载 Bundle(允许笔者用这个词, 这里表示可能是组件, 三方 lib 之类的), 但是想要按需加载, 开发者就要提供加载的条件给 RN, 那么代码很自然的就变成了

const show = false
const Component = show ? require("HumanDetecor")

render() {
  if (show) {
    return Component;
  }
  
  return View;
}

上面代码有些抽象, 但是相信读完 《RAM Bundles and Inline Requires》的小伙伴肯定是可以看懂的, 可以看到, 我们通过 show 变量决定要不要加载 HumanDetecor组件, 那么就要通过 require方法去加载, OK, 一切看上去很美好, 那么上生产环境试试?

const TestApp = () => {
  const Component1 = useRef(null)
  const Component2 = useRef(null)

  const [visible, setVisible] = useState(false)

  const show = () => {
    Component1.current = require("HumanDetecor1")
    Component2.current = require("HumanDetecor2").Test
    setVisible(true)
  }

  return <View>
    { visible && Component1.current }
    { visible && Component2.current }
    <Button title={"显示"} onPress={show}/>
  </View>
}

可以发现, 需要在逻辑代码中混合使用 require, 如果我们希望所有导入都在代码顶部, 就需要使用 inlineRequires

const HumanDetecor1 = require("HumanDetecor1")
const HumanDetecor2 = require("HumanDetecor2").Test

const TestAppWithInlineRequire = () => {
  const Component1 = useRef(null)
  const Component2 = useRef(null)

  const [visible, setVisible] = useState(false)

  const show = () => {
    Component1.current = HumanDetecor1
    Component2.current = HumanDetecor1
    setVisible(true)
  }

  return <View>
    { visible && Component1.current }
    { visible && Component2.current }
    <Button title={"显示"} onPress={show}/>
  </View>
}

可以发现, 通过 inlineRequires可以更好的组织代码. 注: 上面只是以 require举例, inlineRequires同样支持 import语法, 联想到 nextjsURL imports 说不定在将来的某一天可以直接在 React Native 中懒加载 url module.

react native Style property 'height' is not supported by native animated module

这个问题也算是比较常见的问题, 笔者自然是知道 height是不能和 useNativeDriver一同使用的, 本次在调试自行开发的组件 [react-native-dropdown](https://github.com/MonchiLin/react-native-dropdown/tree/next)时遇到的, 复现场景伪代码如下:

  let visible = false
  let animatedStyle = {}
  let value = new Animated.Value(0)

  useEffect(() => {
    if(visible) {
      value.setValue(0)
      setAnimatedStyle({height: value})
      Animated.timing(value, {
        useNativeDriver: false,
        duration: 200,
        toValue: 100
      })
    } else {
      value.setValue(100)
      setAnimatedStyle({transform: { y: value }})
      Animated.timing(value, {
        useNativeDriver: true,
        duration: 200,
        toValue: 0
      })
    }
  }, [visible])

  return <Animated.View style={animatedStyle}/>

通过 visible 状态来控制动画效果, 如果为 true 则播放 height从 0 到 100 的动画, 反之则播放 transform.y 从 100 到 0 的动画, 但是就是这个简单的代码却报出了 react native Style property 'height' is not supported by native animated module 让笔者调试了很长时间. 最初笔者以为是逻辑问题, 调试了很久, 终于确定了和逻辑没关系, useNativeDriver的传参是正确的, 于是只能去看源码, 发现不管怎么设置, 这里都会去验证 style 里是否包含非原生动画属性, 只能又陷入了一段时间的 debug, 虽然没有从源码中参悟原因, 但是无意中发现了一个解决办法, 即: 重新创建 Animated.Value 实例, 虽然没有了解事情的本质, 但是中间经历了很多心路历程, 这里记录下防止别的小伙伴踩坑.

  let visible = false
  let animatedStyle = {}
  let value = new Animated.Value(0)

  useEffect(() => {
    if(visible) {
      // here
      value = new Animated.Value(0)
      setAnimatedStyle({height: value})
      Animated.timing(value, {
        useNativeDriver: false,
        duration: 200,
        toValue: 100
      })
    } else {
      value.setValue(100)
      setAnimatedStyle({transform: { y: value }})
      Animated.timing(value, {
        useNativeDriver: true,
        duration: 200,
        toValue: 0
      })
    }
  }, [visible])

  return <Animated.View style={animatedStyle}/>