美文网首页
从 React 的角度看 Android 的 Jetpack C

从 React 的角度看 Android 的 Jetpack C

作者: 头秃到底 | 来源:发表于2024-04-23 18:01 被阅读0次

    最近为了开发一个小项目,学习了Jetpack Compose,API 设计的很不错。Jetpack Compose的API 非常丰富,正好我的 React 的知识可以发挥作用。也许这就是 React Native 开发者可以代替 Android 原生开发者的原因。

    这两个框架的很多概念和方法虽然名称不同,但是工作原理却大同小异。以下是两者之间概念的对比和解释。

    我们下面把 Jetpack Compose 简称为 JC。

    Component 和 Composable

    React 叫各个组成部分为 Component(组件)。

    function Greeting(props) {
     return <span>Hello {props.name}!</span>;
    }
    
    

    Jetpack Compose 叫各个组成部分为 Composable(其实也是组件)。Composable 方法除了需要是一个方法意外,还需要一个@Composable注解。

    @Composable
    fun Greeting(name: String) {
     Text(text = "Hello $name!")
    }
    
    

    Render 和 Composition

    当一个组件包含的数据发生改变,这些变化的数据需要以定义好的方式绘制到屏幕上。React 的叫做 render,Jetpack Compose 的叫做 Compose。

    Reconciler 和 Composer

    React 内部需要找到组件发生变更的地方才能对应的绘制出来。这个算法叫做Reconciler。JC 也包含着这样的算法,执行这个算法的叫做 Composer。

    State 和 State

    React 和 JC 都把他们的状态叫做 State

    useState 和 State

    React 使用useState来创建 state 变量。它会返回一个 tuple,一个是状态值,一个是这个状态的 setter。

    const [count, setCount] = useState(0);
    
    <button onClick={() => setCount(count + 1)}>You clicked {count} times</button>;
    
    

    JC 使用mutableStateOf方法返回一个MutableState对象,这个对象还包含有一个属性和对应的 getter 和 setter。

    val count = remember { mutableStateOf(0) }
    
    Button(onClick = { count.value++ }) {
      Text("You clicked ${count.value} times")
    }
    
    

    MutableState可以像 React 的useState一样返回 value 和对应的 setter。

    val (count, setCount) = remember { mutableStateOf(0) }
    
    Button(onClick = { setCount(count + 1) }) {
      Text("You clicked ${count} times")
    }
    
    

    为了避免无效计算,remember经常和mutableStateOf一起使用。

    setState 和 Snapshot

    更新 React 的状态的时候可以使用一个方法来实现。如:

    class Button extends React.Component {
      constructor(props) {
        super(props);
        this.state = { count: 0 };
      }
    
      render() {
        <button
          onClick={() => this.setState((state) => ({ count: state.count + 1 }))}
        >
          You clicked {this.state.count} times
        </button>;
      }
    }
    
    

    在 JC 里,这个概念包含在一个叫做Snapshot的类里面。这个类里面有一个enter方法来调用更新后的回调。

    Children Prop 和 Children Composable

    React 和 JC 都把展示在其他 UI 组件里的组件叫做 Children。

    React 是这样的:

    function Container(props) {
      return <div>{props.children}</div>;
    }
    
    <Container>
      <span>Hello world!</span>
    </Container>;
    
    

    JC 是这样的:

    @Composable
    fun Container(children: @Composable () -> Unit) {
      Box {
        children()
      }
    }
    
    Container {
      Text("Hello world"!)
    }
    
    

    Context 和 CompositionLocal

    数据知识沿着组件树传输有的时候过于繁琐。React 可以通过 Context 分享数据。JC 可以用CompositionLocal来实现同样的目的。

    createContext 和 compositionLocal

    React 使用createContext创建 Context 对象。JC 使用compositionLocalOfstaticCompositionLocalOf

    • compositionLocalOf 只有子composition读取它的current值的时候,会在compositionLocal的值改变的时候重绘
    • staticCompositionLocalOf 修改它的值,整个子lambda都会重绘,而不只是读取current的值的地方重绘。

    如果一个compositionLocal的不太会改变的话可以使用staticCompositionLocalOf以获得性能的提升。

    Provider 和 CompositionLocalProvider

    <MyContext.Provider value={myValue}>
      <SomeChild />
    </MyContext.Provider>
    
    

    JC 的实现:

    CompositionLocalProvider(MyLocal provides myValue) {
      SomeChild()
    }
    
    

    useContext 和 CompositionLocal.current

    React:

    const myValue = useContext(MyContext);
    
    

    JC:

    val myValue = MyLocal.current
    
    

    总结一下 android 的写法:

    使用composeLocalOf或者staticCompositionLocalOf(这个一般用于不怎么变化的值)创建一个对象。

    val LocalColor = compositionLocalOf {Color.Red}
    
    

    然后:

    class MyActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            setContent {
                CompositionLocalProvider(LocalColor provides colors) {
                    // ... Content goes here ...
                    ProviderReaderCompositable {
                        Text(text="Read compositable", modifier = Modifier.background(LocalColor.current))
                    }
                }
            }
        }
    }
    
    

    LocalColor可以定义在MyActivity这个文件引用过来,也可以定义在本文件内部,通过CompositionLocalProvider把数据分享出去。

    之后,在子组件中读取数据:

    ProviderReaderCompositable {
        Text(text="Read compositable", modifier = Modifier.background(LocalColor.current))
    }
    
    

    Hooks, Effect

    React允许开发这写自己的 hooks,这样可以把逻辑抽离出来达到重用的效果。这些 hooks 里也可以用其他的 hooks 比如useStateuseEffect

    function useFriendStatus(friendID) {
      const [isOnline, setIsOnline] = useState(null);
    
      useEffect(() => {
        function handleStatusChange(status) {
          setIsOnline(status.isOnline);
        }
    
        ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
        return () => {
          ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
        };
      }, [friendID]);
    
      return isOnline;
    }
    
    

    JC 可以直接在@Composable方法里实现这个功能:

    @Composable
    fun friendStatus(friendID: String): State<Boolean?> {
      val isOnline = remember { mutableStateOf<Boolean?>(null) }
    
      DisposableEffect(friendID) {
        val handleStatusChange = { status: FriendStatus ->
          isOnline.value = status.isOnline
        }
    
        ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange)
        onDispose {
          ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange)
        }
      }
    
      return isOnline
    }
    
    

    useEffect 和 LaunchedEffect

    副作用(side effect)都是作为回调方法执行的。React 和 Jetpack Compose 都是如此。

    useEffect(() => {
      sideEffectRunEveryRender();
    });
    
    

    useEffect的功能比较丰富。JC 把这些功能做了细分。比如:DisposableEffectLaunchedEffectSideEffect

    Clean-up 方法和 DisposableEffect

    组件不再使用的时候需要回收副作用不再使用的资源。React会在useEffect里返回一个clean-up方法:

    useEffect(() => {
      const subscription = source.subscribe(id);
      return () => {
        subscription.unsubscribe(id);
      };
    }, [id]);
    
    

    JC 则使用DisposableEffect

    DisposableEffect(id) {
      val subscription = source.subscribe(id)
      onDispose {
        subscription.unsubscribe(id)
      }
    }
    
    

    useEffect(promise, deps) 和 LaunchedEffect

    JS 使用async关键字创建异步方法。

    useEffect(() => {
      async function asyncEffect() {
        await apiClient.fetchUser(id);
      }
      asyncEffect();
    }, [id]);
    
    

    上面的方法会在id这个依赖项发生改变的时候访问 API 获取数据。React 没有内置取消 promise 的方法,但是可以使用AbortController。如:

    useEffect(() => {
      const controller = new AbortController();
    
      (async () => {
        // The abort signal will send abort events to the API client
        await apiClient.fetchUser(id, controller.signal);
      })();
    
      // Abort when id changes, or when the component is unmounted
      return () => controller.abort();
    }, [id]);
    
    

    在 JC 里,使用的是suspend方法和 coroutine。在LaunchedEffect可以接受传入的参数,他们和 useEffect 里的依赖是同样的作用。如:

    LaunchedEffect(id) {
     apiClient.fetchUser(id)
    }
    
    

    useEffect(callback)和 SideEffect(callback)

    useEffect没有依赖的话,会在每次绘制之后执行。

    useEffect(() => {
      sideEffectRunEveryRender();
    });
    
    

    JC 使用SideEffect实现同样的效果。

    SideEffect {
      sideEffectRunEveryComposition()
    }
    
    

    对于依赖的处理

    总结以上,useEffect可以有带依赖useEffect(() => {}, [deps]), 可以不带依赖useEffect(() => {}),还有一个就是可以带一个空数组当做依赖useEffect(()=> {}, [])

    对应的,在 JC 里可以有

    LaunchedEffect(keys=listOf(deps)) {
        // Run when deps change
    }
    
    

    没有依赖:

    SideEffect {
       // Something...
    }
    
    

    依赖为空数组:

    LaunchedEffect(Unit) {
        // Run only once
    }
    
    

    在上面的一个例子中提到了自定义 hooks。如:

    function useFriendStatus(friendID) {
      const [isOnline, setIsOnline] = useState(null);
    
      useEffect(() => {
        ChatAPI.subscribeToFriendStatus(friendID, (status) => {
          setIsOnline(status.isOnline);
        });
    
        return () => {
          ChatAPI.unsubscribeFromFriendStatus(friendID);
        };
      }, [friendID]);
    
      return isOnline;
    }
    
    

    在上例中,使用了 JC 的DisposableEffect。还可以选择另外一个方法:produceState。如:

    @Composable
    fun friendStatus(friendID: String): State<Boolean?> {
      return produceState(initialValue = null, friendID) {
        ChatAPI.subscribeToFriendStatus(friendID) { status ->
          value = status.isOnline
        }
    
        awaitDispose {
          ChatAPI.unsubscribeFromFriendStatus(friendID)
        }
      }
    }
    
    

    produceState里可以直接对value赋值,这样会调用他的 setter。读取的时候可以在返回值里读取。在produceState里执行的可以是一个 coroutine(协程),或者使用awaitDispose方法来清理资源。后者和useEffect返回一个 clean-up 方法的做法类似。

    Key pros 和 key Composable

    处理列表显示的时候,React和 JC 都需要用到 Key prop。这样才能在这列表里那个发生了,更改、添加或者是删除。Key必须使用唯一值来标记列表里的元素。

    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
    
    

    JC有一个key composable 可以使用:

    Column {
      for (todo in todos) {
        key(todo.id) { Text(todo.text) }
      }
    }
    
    

    .map 和 For 循环

    React 经常使用 map 来显示一列组件:

    function NumberList(props) {
      return (
        <ul>
          {props.numbers.map((number) => (
            <ListItem value={number} />
          ))}
        </ul>
      );
    }
    
    

    JC可以使用 for 循环,也可以使用 forEach,使用 map 也能达到效果:

    @Composable
    fun NumberList(numbers: List<Int>) {
      Column {
        for (number in numbers) {
          ListItem(value = number)
        }
      }
    }
    
    

    使用 forEach 和 map 的例子就不写了,各位可以在测试项目里试试。

    useMemo 和 remember

    React 可以使用useMemo来避免无效运算,只有在依赖发生变更的时候才执行运算。

    const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
    
    

    JS 使用 remember达到同样的效果。依赖可以作为参数传入:

    val memoizedValue = remember(a, b) { computeExpensiveValue(a, b) }
    
    

    条件绘制

    React 里可以使用?:操作符,比如:const a = x === y ? b : c。再比如:

    function Greeting(props) {
      return (
        <span>{props.name != null ? `Hello ${props.name}!` : 'Goodbye.'}</span>
      );
    }
    
    

    对应的,JC 可以使用 kotlin 的语法来实现:

    @Composable
    fun Greeting(name: String?) {
      Text(text = if (name != null) {
        "Hello $name!"
      } else {
        "Goodbye."
      })
    }
    
    

    预览

    React 可以使用storybook来实现。

    JC 可以这样:

    @Composable
    @Preview(showBackground = true)
    fun SettingsScreensPreview() {
        MyTheme() {
            SettingsScreen(null)
        }
    }
    
    

    相关文章

      网友评论

          本文标题:从 React 的角度看 Android 的 Jetpack C

          本文链接:https://www.haomeiwen.com/subject/qnrwxjtx.html