上一篇《4. Mitosis开发必会的CLI命令》介绍了Mitosis的Cli命令,用来编译成不同框架,这一篇来介绍具体开发时的写法。
组件
概览
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,别忘了设置ShdowDom的metaData:
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组件注册了top,left,center。
如果这个组件是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,别忘了设置ShdowDom的metaData:
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
Mitosis的Context必须满足以下三个条件:
- 写在专门创建的文件中
- 文件名必须以
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>
);
}