State

状态管理对任何应用来说都是很重要的一部分内容。 在Qwik里,我们区分了两种类型的状态,reactive和static。

Static状态是任何可以被序列化的东西:a string, number, object, array... 等。 而Reactive状态指的是由useStore()创建的状态。

有一个很重要的点需要知道,Qwik里的状态不是 组件的状态,而是可以被任何组件实例化的 应用的状态。 It's important to notice that state in Qwik is not component state, but rather app state that can be instantiated by any component.

useStore()

const reactive = useStore(initialState) 是一个创建响应式对象的hook。 它需要一个初始化状态作为参数,然后返回一个响应式对象。

useStore()创建的响应式对象和任何其他对象一样,只不过是响应式的。 如果你修改这个对象的一个属性,任何依赖这个属性的组件都会更新。

NOTE Make sure to keep a reference to the reactive object and not only to its properties, for reactivity to work. e.g. doing let { count } = useStore({ count: 0 }) and then mutating count won't trigger updates of components that depends on it.

例子

一个展示useStore怎么用的例子。 是一个计数器的例子,跟踪count变量。

export const App = component$(() => {
  const state = useStore({ count: 0 });

  return (
    <>
      <button onClick$={() => state.count++}>Increment</button>
      Count: {state.count}
    </>
  );
});

上面的例子,App组件使用useStore创建了一个响应式对象。 这个对象用于跟踪count属性,count属性在组件里显示。

如果点击了按钮,count属性值就会改变,因为组件里访问了count属性,因此组件会更新。

嵌套的值

默认下,useStore()只跟踪store的顶层字段。 这意味着你想要组件有更新,就必须更新顶层字段的值。

比如下面的例子,组件就不会更新,点了按钮,UI是没反应的:

import { component$, useStore } from '@builder.io/qwik';

export const App = component$(() => {
  const store = useStore({
    nested: { fields: { are: "not tracked" }}
  })

  return (
    <>
      <p>{store.nested.fields.are}</p>
      <button onClick$={() => store.nested.fields.are = "tracked"}>Click me</button>
    </>
  );
});

为了使组件可以更新,我们就必须更新顶层的nested字段:

store.nested = { fields: { are: { "tracked" } } }

还有第二种方法,为了使上述例子可以工作,我们可以为useStore传递第二个参数{ recursive: true }, 告诉它要递归跟踪store里的所有字段,无论嵌套多深。

export const App = component$(() => {
  const store = useStore({
    nested: { fields: { are: "not tracked" }}
  }, { recursive: true })

  return (
    <>
      <p>{store.nested.fields.are}</p>
      <button onClick$={() => store.nested.fields.are = "tracked"}>Click me</button>
    </>
  );
});

现在组件将如期更新。 并且{ recursive: true }这个设置也会跟踪数组的单个数组项。

import { component$, useStore } from '@builder.io/qwik';

export const App = component$(() => {
  const store = useStore({
    letters: ["A", "B", "C"]
  }, { recursive: true })

  return (
    <>
      {store.letters.map(letter => <p>{letter}</p>)}
      <button onClick$={() => { store.letters[2] = "Z"}}>Click me</button>
    </>
  );
});

传递store到其他组件

Qwik一个很好的功能是可以传递state到其他组件。并且双方都可以对state进行读和写,组件树里的数据流动是双向的。

有三种方式(中文翻译没错,下面只介绍了两种,但是原文档写的是三种方式)传递state到其他组件。

用 props

最朴素的方式是将state作为props给其他组件。 这种方式react也支持,Qwik也支持。

export const Parent = component$(() => {
  const userData = useStore({
    count: 0,
  });

  return (
    <>
      <Child userData={userData} />
    </>
  );
});

export const Child = component$(({ userData }) => {
  return (
    <>
      <button onClick$={() => userData.count++}>Increment</button>
      Count: {userData.count}
    </>
  );
});

用 context API

context API是一种不需要通过props就能把state传递到其他组件的方式。 所有后继组件(子、孙、重孙)都能访问到这个state,并且都能对它进行读和写操作。

更多信息请参考context API

const CTX = createContext('stuff');

export const Stores = component$(() => {
  const userData = useStore({
    count: 0,
  });

  useContextProvider(CTX, userData);

  return (
    <>
      <Child />
    </>
  );
});

export const Child = component$(() => {
  const userData = useContext(CTX);
  return (
    <>
      <button onClick$={() => userData.count++}>Increment</button>
      Count: {userData.count}
    </>
  );
});

计算属性

计算属性是从其他值派生出来的值。 计算属性是很有用的。

Qwik有两种方式创建计算属性,使用useWatch$() 或者 useResource$()

这两个的只要区别是useWatch$()允许副作用而且执行过程是串行的。 而useResource$()是异步的,多个useResource调用是可以并行的。

useWatch$()通常用于计算中间state,而useResource$()更擅长计算最终state,用于渲染。 让我们看看下面的例子:

useWatch$() 例子

export const App = component$(() => {
  const state = useStore({
    count: 0,
    doubleCount: 0
  });

  useWatch$(({ track }) => {
    track(() => state.count);
    state.doubleCount = state.count * 2;
  });

  return (
    <>
      <button onClick$={() => state.count++}>Increment</button>
      Count: {state.count}
      Count * 2: {state.doubleCount}
    </>
  );
});

useResource$() 例子

export const App = component$(() => {
  const state = useStore({
    count: 0,
  });

  const doubleCount = useResource$(({ track }) => {
    track(() => state.count);
    return state.count * 2;
  });

  return (
    <>
      <button onClick$={() => state.count++}>Increment</button>
      Count: {state.count}
      Count * 2: {doubleCount.promise}
    </>
  );
});

useWatchuseResource 都有专门的文档来解释。

Reactivity

由于Qwik的细粒度响应性,只有依赖state的组件才会更新。这是巨大的性能提升,因为只有需要更新的组件才会更新。

Made with ❤️ by