It is well known that the design concept of Jetpack Compose and many of the team members come from React. In terms of the API, it references a lot of React (Hooks) design. By comparing with React, you can get familiar with Compose’s related functions.

Compose is currently in the alpha version, and although the API will be adjusted, it is already functionally aligned with React. This article is based on 1.0.0-Alpha11.


React Component vs Composable

Components in React become the basic unit for separating UI, especially function components introduced by Hooks after 16.8, which are better at decoupling UI from logic than class components. The function component is a function that takes Props as an argument and returns a JSX node:

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

Compose also uses functions as components: a function annotated with @composable. In addition, Kotlin’s DSL implements declarative syntax without additional introduction of other markup languages such as JSX, which is much simpler than React:

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


JSX vs DSL

DSLS are much more compact than JSX and can directly represent logic using native syntax.

loop

Implementing a loop logic in JSX, for example, requires a mix of both languages

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

A loop in a DSL is a normal for loop

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

If statement

JSX uses ternary operators to represent conditions

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

The DSL uses IF expressions directly

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

key component

React and Compose can both use keys to mark specific components in the list, narrowing the scope for redrawing.

JSX uses the key attribute

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

DSLS use key components to identify components

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


Children Prop vs Children Composable

As mentioned earlier, React and Compose both use function components to create uIs. The difference is that one uses DSL and the other relies on JSX.

In React, the child component is passed in through the children field of props

function Container(props) {
  return <div>{props.children}</div>;
}

<Container>
  <span>Hello world!</span>
</Container>;
Copy the code

In Compose, the child component is passed in as the @composable function

@Composable
fun Container(children: @Composable() - >Unit) {
  Box {
    children()
  }
}

Container {
  Text("Hello world"!). }Copy the code


Context vs Ambient(CompositionLocal)

For functional components, it is recommended to pass data using props/parameter, but allow some global data to be shared between components. React uses Context to store global data, and Compose uses Ambient (renamed CompositionLocal in Alpha12) to store global data

createContext : ambientOf

React creates a Context using createContext:

const MyContext = React.createContext(defaultValue);
Copy the code

Compose creates Ambient using Ambient of:

val myValue = ambientOf<MyAmbient>()
Copy the code

Provider : Provider

Both React and Compose use providers to inject global data for child components to access

<MyContext.Provider value={myValue}>
  <SomeChild />
</MyContext.Provider>
Copy the code
Providers(MyAmbient provides myValue) {
  SomeChild()
}
Copy the code

useContext : Ambient.current

The React neutron component accesses the Context using the useContext hook

const myValue = useContext(MyContext);
Copy the code

The Compose neutron component accesses the Ambient through a singleton object

val myValue = MyAmbient.current
Copy the code


useState vs State

Whether React or Compose, state management is critical.

React uses useState hook to create State

const [count, setCount] = useState(0);

<button onClick={()= > setCount(count + 1)}>
  You clicked {count} times
</button>
Copy the code

Compose uses mutableStateOf to create a state, which can also be obtained by the BY proxy

val count = remember { mutableStateOf(0) }

Button(onClick = { count.value++ }) {
  Text("You clicked ${count.value} times")}Copy the code

You can also get/set separately by deconstructing it

val (count, setCount) = remember { mutableStateOf(0) }

Button(onClick = { setCount(count + 1) }) {
  Text("You clicked ${count} times")}Copy the code

Or by proxy

var count : Int  by remember { mutableStateOf(false) }

Button(onClick = { count++ }) {
  Text("You clicked ${count} times")}Copy the code

Compose creates state often using remeber{} to avoid creating state repeatedly when redrawing, the equivalent of useMemo


useMemo vs remember

React uses the useMemo hook to store values that cannot be recomputed over and over again, only when parameters change.

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

Remember is used to realize the same function in Compose, and parameters are also used as the judgment conditions for recalculation

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


useEffect vs SideEffect

A function component meets the requirements of a pure function: it has no side effects, no state, and no impact even if it is run multiple times. But there is always logic that cannot be executed as a pure function, such as life cycle callbacks, logging, subscriptions, timing, etc., that can only be executed at certain times and cannot be executed as many times as a pure function can without side effects.

React useEffect provides a hook point that is executed on each render. Note that this is different from writing directly outside, there is no need to rerender when diff does not change, so useEffect is not required

useEffect(() = > {
  sideEffectRunEveryRender();
});
Copy the code

Compose uses SideEffect to handle side effects (earlier version onCommit{})

SideEffect {
  sideEffectRunEveryComposition()
}
Copy the code

UseEffect (callback, deps) : DisposableEffect

As with useMemo, parameters can be accepted, and only executed when the parameters change each time render:

useEffect(() = > {
  sideEffect();
}, [dep1, dep2]);
Copy the code

Logic that is only executed on the first render (equivalent to onMount) can be handled as follows:

useEffect(() = >{ sideEffectOnMount(); } []);Copy the code

Make DisposableEffect in Compose:

DisposableEffect(
   key1 = "",...). { onDispos{} }Copy the code

Clean-up function : onDispose

UseEffect is postprocessed by returning a function

useEffect(() = > {
  const subscription = source.subscribe();
  return () = > {
    subscription.unsubscribe();
  };
});
Copy the code

DisposableEffect through a post-processing DisposableEffectDisposable:

DisposableEffect() {
  val dispose = source.subscribe()
  onDispose { / / return DisposableEffectDisposable
     dispose.dispose()
  }
}
Copy the code


Hook vs Effect

React allows custom Hooks to encapsulate reusable logic. Hooks can call useState, useEffect, and other Hooks release methods to complete logic for a specific lifecycle. Custom Hooks are named using the form useXXX

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() = > {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () = > {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}
Copy the code

Compose has no naming requirement, and any @composable function can be used to implement a reusable piece of Effect logic:

@Composable
fun friendStatus(friendID: String): State<Boolean? > {val isOnline = remember { mutableStateOf<Boolean? > (null) }

  DisposableEffect {
    val handleStatusChange = { status: FriendStatus ->
      isOnline.value = status.isOnline
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange)
    
    onDispose {
		ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange)
	}
  
  }

  return isOnline
}
Copy the code