学习《SolidJS》(四)内置组件

129 阅读4分钟

内置组件

Show

在React中,如果某个组件是否显示是根据state来控制,或许会

import { useState } from 'react'

function BoolControl() {
  const [show, setShow] = useState(false)

  return (
    <div>
      <button onClick={() => setShow(prevState => !prevState)}>点我</button>
      {show && <div>你看得见吗?</div>}
    </div>
  )
}

在SolidJS中,提供了Show组件来实现这样的效果。当然,不是说必须使用Show组件,上面的写法仍然是支持的,不过我个人认为下面这样写语义上更清晰一些。

import { createSignal, Show } from "solid-js";

function BoolControl() {
  const [show, setShow] = createSignal(false)

  return (
    <div>
      <button onClick={() => setShow(prevState => !prevState)}>点我</button>
      <Show when={show()}>
        <div>你看得见吗?</div>
      </Show>
    </div>
  )
}

在Show组件中有when这个属性,这个属性就是用来控制Show的children是否显示的。另外它还有一个属性fallback 这个接收的是JSX.Element,当when为false的时候,就显示fallback的内容。

Switch

在写组件的时候,一般来说使用Show组件基本上已经够用来实现,if或者if-else的情况。但是有些时候还是需要用到类似于switch-case的情况的,又或者是if-elseif-elseif...这样的情况。比如是Status不同的时候,要显示当前状态它们颜色不同,或者显示一些可交互的组件等等。这个时候就需要用到Switch,比如下面这样写

import {createSignal, Match, Switch} from "solid-js";

enum Status {
  NotApplied,
  ToAudit,
  Fail,
  Passed
}

function operation() {
  const [status, setStatus] = createSignal(Status.NotApplied);

  return (
    <Switch>
      <Match when={status() === Status.NotApplied}>
        <button onClick={() => console.log("打开申请弹窗")}>去申请</button>
        <button onClick={() => console.log("打开申请要求")}>查看申请条件</button>
      </Match>
      <Match when={status() === Status.ToAudit}>
        <button onClick={() => console.log("显示当前流程")}>查看当前流程</button>
        <button onClick={() => console.log("显示当前流程")}>查看当前流程</button>
      </Match>
      <Match when={status() === Status.Passed || status() === Status.Fail}>
        <button onClick={() => console.log("显示申请结果")}>查看申请结果</button>
      </Match>
    </Switch>
  )
}

Switch要与Match搭配使用,Switch也有fallback属性,当所有条件都不匹配的时候,就会显示fallback的内容,也就是说它相当于default的效果。如果没有fallback,也没有满足任何条件,就是什么都不显示。

Dynamic

虽然Switch挺好用的,但是有些时候要切换的组件非常的多,后续可能还会继续增加。那么如果使用Switch就需要经常修改,而且代码非常的长,不好维护。这个时候就会想到使用object或者map,经组件保存起来,然后根据名称显示对应的组件,这个时候就要用到了Dynamic,用法如下(样例来自SolidJs官网)

import { Dynamic } from "solid-js/web";
import { createSignal, For } from "solid-js";

const RedThing = () => <strong style="color: red">Red Thing</strong>;
const GreenThing = () => <strong style="color: green">Green Thing</strong>;
const BlueThing = () => <strong style="color: blue">Blue Thing</strong>;

const options = {
  red: RedThing,
  green: GreenThing,
  blue: BlueThing
}

function ColorComponent() {
  const [selected, setSelected] = createSignal("red");

  return (
    <>
      <select value={selected()} onInput={e => setSelected(e.currentTarget.value)}>
        <For each={Object.keys(options)}>{
          color => <option value={color}>{color}</option>
        }</For>
      </select>
      <Dynamic component={options[selected()]} />
    </>
  );
}

For

循环一个数组,然后创建出对应的组件,这是经常用到的。在React里面就是直接使用数组的map来实现。 在SolidJS还可以使用For,比如下面代码这样

import { createSignal, For } from "solid-js";

function ForExample(){
    const [lans, setLans] = createSignal(["JAVA", "Rust", "C", "Javascript", "Typescript"])
    
    return (
        <ul>
            <For each={lans()}>
                {
                    (lan, i) => (
                        <li>
                            {i()}.{lan}
                        </li>
                    )
                }
            </For>
        </ul>
    )
}

For有each这个属性,它就是接收一个数组的。然后children是一个函数,这个函数的参数是当前数组项和当前索引,返回的是JSX.Element。其实这里让我们知道,children的类型其实可以是任意,主要看的就是组件怎么处理。 另外它也有fallback,接收的也是JSX.Element,当each时null,false,undefined,空数组的时候。就显示fallback的内容。

还有React和Vue的开发者大家可能发现了,li标签没有key,要注意在SolidJS中这是没必要的。

Index

它和For的很像,上面修改后如下

import {createSignal, Index} from "solid-js";

function ForExample(){
  const [lans, setLans] = createSignal(["JAVA", "Rust", "C", "Javascript", "Typescript"])

  setTimeout(() => {
    setLans(["Rust", "C", "Javascript", "Typescript", "JAVA", ])
  }, 1000 * 5)

  return (
    <ul>
      <Index each={lans()}>
        {
          (lan, i) => (
            <li>
              {i}.{lan()}
            </li>
          )
        }
      </Index>
    </ul>
  )
}

可以发现区别就是For中,lan是一个值,而i是一个函数。Index中lan是一个函数,i是一个值。 这里的函数其实是Singal,所以说For和Index中依赖项不一样了。 如果这里使用的不是li而是input,那么使用For的话,每次修改input的value,那么都需要销毁input,重建input。改成Index就不会有这个问题,它只会更新input的value。

Portal

传送组件,主要目的提供不渲染在当前组件的父组件下的。比如自定义下拉框\选择框,如果渲染在当前组件的父组件下,有可能高度不够,不希望受到父组件的影响。又比如渲染一个弹窗,这个时候,需要讲dom挂载到body标签下的。 比如弹窗可以这么写,

import {Portal} from "solid-js/web";

function Popup() {
  return (
    <Portal mount={document.body}>
      <div style={{
        display: "flex",
        "justify-content": "center",
        "align-items": "center",
        position: "absolute",
        top: "50%",
        left: "50%",
        height: "50%",
        width: "50%",
        "background-color": "rgba(255, 255, 255, 0.5)",
        transform: "translate(-50%, -50%)"
      }}>
        <div style={{"font-size": "30px"}}>
          这是一个弹窗
        </div>
      </div>
    </Portal>
  )
}

ErrorBoundary

错误边界,这个组件是用来捕捉异常的。下面就是使用样例,可以看到,不管错误发生在组件第一次运行,还是后续渲染的过程中,都可以捕捉到。

const BrokenOnComponent = (props) => {
  throw new Error("Broken On Component");
  return <>Never Getting Here{throwError()}</>
}

const BrokenOnRender = (props) => {
  const throwError = () => {throw new Error("Broken On Render");}
  return <>Never Getting Here{throwError()}</>
}

function ErrorBoundaryExample() {
  return (
    <>
      <div>Before</div>
      <ErrorBoundary fallback={(err) => err}>
        <BrokenOnComponent />
      </ErrorBoundary>
      <ErrorBoundary fallback={(err) => err}>
        <BrokenOnRender />
      </ErrorBoundary>
      <div>After</div>
    </>
  );
}

最后

内置组件就暂时这样先吧!更加深入的理解还需要后续的学习和介绍,才可以写成文章分享给大家。