5. Mitosis开发特性大全

154 阅读4分钟

上一篇《4. Mitosis开发必会的CLI命令》介绍了MitosisCli命令,用来编译成不同框架,这一篇来介绍具体开发时的写法。

组件

概览

Mitosis参考了许多现代前端框架。写Mitosis时,你会看到像React一样的hook,像Vue一样的可变状态,像Solid一样的JSX静态表单,像Svelte一样的编译方式,还有像Angular一样的简单的描述性结构。 下面的例子展示了Mitosis的一些特性。

import { For, Show, useStore } from '@builder.io/mitosis';

export default function MyComponent(props) {
  const state = useStore({
    newItemName: 'New item',
    list: ['hello', 'world'],
    addItem() {
      state.list = [...state.list, state.newItemName];
    },
  });

  return (
    <div>
      <Show when={props.showInput}>
        <input
          value={state.newItemName}
          onChange={(event) => (state.newItemName = event.target.value)}
        />
      </Show>
      <div css={{ padding: '10px' }}>
        <button onClick={() => state.addItem()}>Add list item</button>
        <div>
          <For each={state.list}>{(item) => <div>{item}</div>}</For>
        </div>
      </div>
    </div>
  );
}

组件

就像大部分现代前端框架一样,Mitosis也是组件驱动, 它接收CSS属性按如下图中的驼峰拼写:

export default function CSSExample() {
  return <div css={{ marginTop: '10px', color: 'red' }} />;
}

样式

css

你同样可以将media query作为key,样式的对象作为值:

export default function ResponsiveExample() {
  return (
    <div
      css={{
        marginTop: '10px',
        '@media (max-width: 500px)': {
          marginTop: '0px',
        },
      }}
    />
  );
}

class vs className

Mitosis偏向于用class,但同样支持className。如果两者都定义了,将合并。Mitosis推荐仅采用一个。

状态

状态由hookuseStore提供。目前只能使用名字为state的常量:

export default function MyComponent() {
  const state = useStore({
    name: 'Steve',
  });

  return (
    <div>
      <h2>Hello, {state.name}</h2>
      <input onInput={(event) => (state.name = event.target.value)} value={state.name} />
    </div>
  );
}

如果初始状态是一个计算出来的值(不管是基于props或是一些函数的输出),那么你可以使用getter方法:

import { kebabCase } from 'lodash';

export default function MyComponent(props) {
  const state = useStore({
    name: 'Steve',
    get transformedName() {
      return kebabCase('Steve');
    },
    get transformedName() {
      return props.name;
    },
  });

  return (
    <div>
      <h2>Hello, {transformedName(state.name)}</h2>
      <input onInput={(event) => (state.name = event.target.value)} value={state.name} />
    </div>
  );
}

当状态值改变时组件会自动更新。

函数

状态对象同样可以接收函数。

export default function MyComponent() {
  const state = useStore({
    name: 'Steve',
    updateName(newName) {
      state.name = newName;
    },
  });

  return (
    <div>
      <h2>Hello, {state.name}</h2>
      <input onInput={(event) => state.updateName(event.target.value)} value={state.name} />
    </div>
  );
}

控制流

Solid类似,Mitosis的控制流是静态的。不像React那样自由地使用JS,Mitosis需要你使用控制流组件比如<Show><For>

Show

Show组件,在Angular体现为指令*ngIf,是按如下类型定义的:

export declare function Show<T>(props: {
  when: T | undefined | null | false;
  else?: JSX.Element;
  children?: JSX.Element | null;
}): any;

<Show>组件作为条件判断,它接收一个when参数作为匹配条件。当条件匹配时,子组件会显示出来,否则子组件不显示,如果else定义了的话则会显示出来。

export default function MyComponent(props) {
  return (
    <>
      <Show when={props.showContents} else={<span {...props.attributes}>{props.text}</span>}>
        Hello, I may or may not show!
      </Show>
      ;
    </>
  );
}

For

For组件,用来重复渲染比如一个数组,在Angular体现为指令*ngFor。它接收一个数组作为each参数,组件内包含一个单一函数,这个函数接收遍历的每一项和index,并输出你想要定义的组件:

export default function MyComponent(props) {
  const state = useStore({
    myArray: [1, 2, 3],
  });
  return <For each={state.myArray}>{(theArrayItem, index) => <div>{theArrayItem}</div>}</For>;
}

Children

我们用标准的方式props.children给子组件传参:

export default function MyComponent(props) {
  return <div>{props.children}</div>;
}

如果你要开发的是Web Component,别忘了设置ShdowDommetaData

import { useMetadata } from '@builder.io/mitosis';

useMetadata({
  isAttachedToShadowDom: true,
});
export default function MyComponent(props) {
  return <div>{props.children}</div>;
}

Slot

当你想要注册一个命名过的插槽,可以使用slot参数。

<div>
  <Layout
    slotTop={<NavBar/>}
    slotLeft={<Sidebar/>}
    slotCenter={<Content/>}
  />
    anything else
  </Layout>
</div>

在上面的例子中,我们给layout组件注册了topleftcenter。 如果这个组件是Mitosis组件,那么我们可以这样使用组件的参数:

export default function Layout(props) {
  return (
    <div className="layout">
      <div className="top">{props.slotTop}</div>
      <div className="left">{props.slotLeft}</div>
      <div className="center">{props.slotCenter}</div>
      {props.children}
    </div>
  );
}

或者直接使用Mitosis提供的Slot组件:

import { Slot } from '@builder.io/mitosis';

export default function Layout(props) {
  return (
    <div className="layout">
      <div className="top">
        <Slot name="top" />
      </div>
      <div className="left">
        <Slot name="left" />
      </div>
      <div className="center">
        <Slot name="center" />
      </div>
      <Slot />
    </div>
  );
}

当这个组件编译为Vue组件后,将是这样:

<div class="layout">
  <div class="top"><slot name="top" /></div>
  <div class="left"><slot name="left" /></div>
  <div class="center"><slot name="center" /></div>
  <slot />
</div>
}

当这个组件编译为Angular组件后,将是这样:

<div>
  <layout>
    <sidebar left></sidebar>
    <nav-bar top></nav-bar>
    <content center></content>
    anything else
  </layout>
  <div></div>
</div>
@Component({
  selector: 'layout',
  template: `
    <div class="layout">
      <div class="top">
        <ng-content select="[top]"></ng-content>
      </div>
      <div class="left">
        <ng-content select="[left]"></ng-content>
      </div>
      <div class="center">
        <ng-content select="[center]"></ng-content>
      </div>
      <ng-content></ng-content>
    </div>
  `,
})
class LayoutComponent {}

如果你要开发的是Web Component,别忘了设置ShdowDommetaData

import { useMetadata } from '@builder.io/mitosis';

useMetadata({
  isAttachedToShadowDom: true,
});
export default function Layout(props) {
  return (
    <div className="layout">
      <div className="top">{props.slotTop}</div>
      <div className="left">{props.slotLeft}</div>
      <div className="center">{props.slotCenter}</div>
      {props.children}
    </div>
  );
}

默认的Slot内容

import { Slot } from '@builder.io/mitosis';

export default function Layout(props) {
  return (
    <div className="layout">
      <div className="top">
        <Slot name="top">Top default</Slot>
      </div>
      <div className="left">
        <Slot name="left" />
      </div>
      <div className="center">
        <Slot name="center" />
      </div>
      <Slot>Default child</Slot>
    </div>
  );
}

Context

MitosisContext必须满足以下三个条件:

  • 写在专门创建的文件中
  • 文件名必须以context.lite.ts结尾
  • 默认的export必须是一个函数,而这个函数必须返回一个context对象

例子:

// simple.context.lite.ts
import { createContext } from '@builder.io/mitosis';

export default createContext({
  foo: 'bar',
  get fooUpperCase() {
    return this.foo.toUpperCase();
  },
  someMethod() {
    return this.fooUpperCase.toLowercase();
  },
  content: null,
  context: {} as any,
  state: {},
});

然后你就可以在你的组件里用context了:

import { setContext, useContext } from '@builder.io/mitosis';
import Context from './simple.context.lite';

export default function ComponentWithContext(props: { content: string }) {
  // you can access the context using `useContext`
  const foo = useContext(Context);

  // you can use `setContext` to provide a new value for the context
  setContext(Context, {
    foo: 'baz',
    content() {
      return props.content;
    },
  });

  return (
    // you can also use `Context.provider` to provide a new value for the context
    <Context.Provider value={{ bar: 'baz' }}>{foo.value}</Context.Provider>
  );
}

下一篇:《6. Mitosis hooks用法大全》

参考

github.com/BuilderIO/m… github.com/BuilderIO/m…