SolidJS 学习

1,654 阅读7分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 9 月更文挑战」的第10天,点击查看活动详情 >>

官方文档:

www.solidjs.com/docs/latest…

本篇文章对应源代码:

gitee.com/achens/my-s…

搭建项目:

  1. 使用 npx 安装 solid 开发环境。
npx degit solidjs/templates/ts my-app
  1. 使用 degit 搭建 solid 开发环境。
npm i -g degit

degit solidjs/templates/js my-app

// ts 环境

degit solidjs/tempaltes/ts my-app

npx degit solidjs/templates/ts my-app

// ts 环境
  1. App.jsx 根页面
import logo from './logo.svg';
// 引入 css module
import styles from './App.module.css';

function App() {
  return (
    // 使用 style.module 中的类名
    <div class={styles.App}>
      <header class={styles.header}>
        <img src={logo} class={styles.logo} alt="logo" />
        <p>
          Edit <code>src/App.jsx</code> and save to reload.
        </p>
        <a
          class={styles.link}
          href="https://github.com/solidjs/solid"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn Solid
        </a>
      </header>
    </div>
  );
}

export default App;

一、响应式 API 与生命周期

import styles from "./assets/styles/App.module.css";

import Responsive from "./component/responsive";

function App() {
  return (
    <div class={styles.App}>
      // 传值给子组件
      <Responsive value="你好呀!Responsive。"></Responsive>
    </div>
  );
}

export default App;

1. 响应式基础 API

1. createSiganl

创建一个响应式对象,可以看做是 React 中的 useState()。

import { createSignal } from "solid-js";

const Signal = () => {
  const [name, setName] = createSignal("我是 signal");

  const onUpdateName = (e) => {
    setName((prev) => `${prev} + ${e.target.innerHTML}`);
  };

  return (
    <div class="signal">
      {/* 亦或者使用 name() 来显示 */}
      <p onClick={(e) => setName("君有云兮")}>{name}</p>
      <p onClick={(e) => onUpdateName(e)}>点我修改</p>
    </div>
  );
};

export default Signal;

2. createEffect

创建一个 Effect 自动追踪每一次的 signal 更新,Effect 中的回调函数会在每一次组件渲染完成后执行。

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

const effect = () => {
  const [name, setName] = createSignal("我是 effect");

  createEffect(() => {
    {/* 值得注意的是在 effect 中必须已函数形式调用 signal */}
    console.log(name());
  });

  return <div class="effect">{name}</div>;
};

export default effect;

3. createMemo

Memo 会缓存其回调函数中使用到的 signal,并在每一次函数触发时检测 signal 是否发生改变,如果 signal 没有改变,就不会执行其回调函数。

下面通过一个输出 count 值的实例来演示下在 solid.js 中 createMemo 的作用。

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

const memo = () => {
  const [count, setCount] = createSignal(10);

  const onUpdateFiber = () => {
    console.log("你好");
  };

  const onFibonacci = (num) => {
    if (num <= 1) return 1;
    return onFibonacci(num - 1) + onFibonacci(num - 2);
  };

  // 不使用 createMemo 的话,
  // 组件每次渲染和 onCounter 函数触发时都会输出二十遍 值发生了改变
  const onCounter = () => {
    console.log("值发生了改变");
    return onFibonacci(count());
  };

  return (
    <div class="memo">
      <div class="box-fiber">
        <button onClick={() => setCount(count() + 1)}>Count: {count()}</button>
        <div>
          1. {onCounter()} {onCounter()} {onCounter()} {onCounter()}{" "}
          {onCounter()}
        </div>
        <div>
          2. {onCounter()} {onCounter()} {onCounter()} {onCounter()}{" "}
          {onCounter()}
        </div>
        <div>
          3. {onCounter()} {onCounter()} {onCounter()} {onCounter()}{" "}
          {onCounter()}
        </div>
        <div>
          4. {onCounter()} {onCounter()} {onCounter()} {onCounter()}{" "}
          {onCounter()}
        </div>
      </div>
    </div>
  );
};

export default memo;

可以看到每次点击 Count 按钮,onCounter 就会执行 20 次。

使用了 createMemo 后,只有我在主动点击时才会触发 onCount。

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

const memo = () => {
  const [count, setCount] = createSignal(10);

  const onUpdateFiber = () => {
    console.log("你好");
  };

  const onFibonacci = (num) => {
    if (num <= 1) return 1;
    return onFibonacci(num - 1) + onFibonacci(num - 2);
  };

  // 使用 createMemo 时,组件渲染并不会触发 onCounter,
  // 并且每次触发 onCounter 函数都只会输出一遍 值发生了改变
  const onCounter = createMemo(() => {
      console.log("值发生了改变");
      return onFibonacci(count());
  });

  return (
    <div class="memo">
      <div class="box-fiber">
        <button onClick={() => setCount(count() + 1)}>Count: {count()}</button>
        <div>
          1. {onCounter()} {onCounter()} {onCounter()} {onCounter()}{" "}
          {onCounter()}
        </div>
        <div>
          2. {onCounter()} {onCounter()} {onCounter()} {onCounter()}{" "}
          {onCounter()}
        </div>
        <div>
          3. {onCounter()} {onCounter()} {onCounter()} {onCounter()}{" "}
          {onCounter()}
        </div>
        <div>
          4. {onCounter()} {onCounter()} {onCounter()} {onCounter()}{" "}
          {onCounter()}
        </div>
      </div>
    </div>
  );
};

export default memo;

4. createResource

创建一个管理异步请求的 signal。

import { createResource } from "solid-js";

const Resource = () => {
  const getUserList = async () =>
    (await fetch("http://127.0.0.1:4523/mock/985025/user/list")).json();

  const [data, { mutate, refetch }] = createResource(getUserList);

  {/* 检查是否报错 */}
  if (!data.error) console.log("请求出错!");
 
  {/* 无需创建 promise 直接设置值 */}
  mutate("achens1");

  {/* 重复执行最后的请求 */}
  refetch();
  
  return (
    {/* 输出值 */}
    <div class="resource">请求到的数据:{JSON.stringify(data())}</div>
  );
};

export default Resource;

2. 生命周期

1. onMount

模板挂载时执行,一般用于页面初始化时发送请求拿到数据进行输出。

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

const USER_SEX = {
  0: "男",
  1: "女",
  2: "未知",
};

const Mount = () => {
  const [userList, setUserList] = createSignal({});

  const getUserList = () =>
    fetch("http://127.0.0.1:4523/mock/985025/user/list");

  onMount(async () => {
    await getUserList()
      .then((response) => response.json())
      .then((response) => {
        setUserList(response.user_list);
      });
  });

  return (
    <div class="mount">
      {/* solid 中通过内置组件 For 来渲染数据 */}
      <For each={userList()}>
        {(item) => (
          <div className="userInfo">
            <p>id:{item.id}</p>
            <p>名称:{item.name}</p>
            <p>性别:{USER_SEX[item.sex]}</p>
          </div>
        )}
      </For>
    </div>
  );
};

export default Mount

2. onCleanup

状态销毁或重新计算时触发,一般都是在这里清空定时器等异步任务。

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

const Cleanup = () => {
  const [count, setCount] = createSignal(0);

  const timer = setInterval(() => {
    setCount(count() + 1);
  }, 1000);

  onCleanup(() => {
    clearInterval(timer);
  });

  return (
    <div class="cleanup">
      <p>Count:{count()}</p>
    </div>
  );
};

export default Cleanup;

3. onError

子作用域发生报错时触发。

import { onError } from "solid-js";

import Mount from "./Mount";
import Cleanup from "./Cleanup";

const LifeCycle = (error) => {
  onError(() => {
    console.log("子组件报错啦:", error);
  });

  return <div class="life-cycle">
      <Mount></Mount>
      <Cleanup></Cleanup>
  </div>;
};

export default LifeCycle;

二、JSX 特殊属性值

1. 事件绑定、动态 style、动态 class、classList

import { createSignal } from "solid-js";

const attribute = () => {
  const [textColor, setTextColor] = createSignal("red");

  const onLogName = (name) => {
    console.log(name ? name : "achens");
  };

  return (
    <div class="attribute">
      {/* 必须以函数形式来绑定时间 */}
      <p onClick={() => onLogName()}>点我输出 achens</p>
      {/* 数组形式绑定事件,可同时绑定多个事件 */}
      <p onClick={[onLogName, "君有云兮"]}>点我输出 君有云兮</p>
      {/* 动态样式绑定 */}
      <p style={{ color: textColor() }} onClick={() => setTextColor("orange")}>
        点我修改颜色
      </p>
      {/* 动态类名绑定,也可以使用 class;个人建议动态类名绑定就使用 className,
          与正常类名定义区分开来 */}
      <p className={textColor() === "orange" ? "orange" : ""}>查看动态类名</p>
      {/* classList 用于同时定义多个动态类名 */}
      <p
        classList={{
          red: textColor() == "red" ? "red" : "",
          orange: textColor() == "orange" ? "orange" : "",
        }}
      >
        查看 classList
      </p>
    </div>
  );
};

export default attribute;

2. ref 绑定元素

值得注意的是 ref 是在模板渲染的时候去绑定元素的,所以不能在定义的时候就使用。

在模板渲染时,solid 会将使用了 ref 的元素渲染为下方的形式。

const myDom =

你好!

;

const attribute = () => {
  const onDom = () => {
    // 输出 p 标签
    console.log(myDom);
  };

  let myDom;
  // 输出 undefined
  console.log(myDom);

  return ( <p ref={myDom} onClick={() => onDom()}>你好!</p> );
};

export default attribute;

\

3. use: 自定义指令

solidJS 中通过一下形式来定义自定义指令。

use: 事件名称 = { 传参 }

三、内置组件 Component

1. 渲染流组件

1.

遍历对象时推荐使用。

import { createSignal, For } from 'solid-js'

const Fors = () => {
  const [forList, setForList] = createSignal([
    {id: 0, name: '你好'},
    {id: 1, name: '我好'},
    {id: 2, name: '君有云兮'},
    {id: 3, name: 'achens'}
  ])
  
  return (
    <For each={forList()}>
      {(item, i) => (
        <li>{item.id}+{ item.name }+{i() + 1}</li>
      )}
    </For>
  )
}

export default Fors

2.

在 Index 组件中,素组的索引是列表的实际键。遍历数组更加推荐 。

import { createSignal, Index } from 'solid-js'

const Index = () => {
  const [IndexList, setIndexList] = createSignal([0, 1, 2, 3])
  
  return (
    <Index each={IndexList()}>
      {(item, i) => (
        <li>{item()}+{i+1}</li>
      )}
    </Index>
  )
}

export default Index

2. 控制流组件

1.

  • when:显示条件。
  • fallback:切换显示条件时的回调。
import { createSignal, Show } from 'solid-js'

const Shows = () => {
    const [isShow, setIsShow] = createSignal(false)
    const toggle = () => setIsShow(!isShow())

    return (
        <Show when={isShow()}
              fallback={() => <button onClick={toggle}>Log in</button>}
        >
            <button onClick={toggle}>Log out</button>
        </Show>
    )
}

export default Shows

2. /

适用于多条件判断下的组件。

import { createSignal, Switch, Match } from 'solid-js'

const Switchs = () => {
    const [age] =  createSignal(11)

    return (
        // 不满足任何条件时显示 fallback 中的内容
        <Switch fallback={<span>{age()} is 5 ~ 10</span>}>
            <Match when={age() > 10}>
                <p>{age()} is > 10</p>
            </Match>
            <Match when={age() > 5}>
                <p>{age()} is > 5</p>
            </Match>
        </Switch>
    )
}

export default Switchs

3. 功能性组件

1.

捕获子组件中任何位置产生的 JavaScript 错误,并将这个错误记录下来,同时回退 UI 而非显示崩溃的组件树。

2.

可以理解为非阻塞式加载异步组件,Suspense 加载所包含的异步组件时不会影响页面的渲染。而会显示一个回退占位符。

// 在其包含的异步组件加载完成之前会一直显示 Loading...
<Suspense fallback={<div>Loading...</div>}>
  <AsyncComponent />
</Suspense>

3. (实验性组件)

一个实验性 API,用于协调多个异步组件之间的加载,官网解释:

www.solidjs.com/docs/latest…

<SuspenseList revealOrder="forwards" tail="collapsed">
  <ProfileDetails user={resource.user} />
  <Suspense fallback={<h2>Loading posts...</h2>}>
    <ProfileTimeline posts={resource.posts} />
  </Suspense>
  <Suspense fallback={<h2>Loading fun facts...</h2>}>
    <ProfileTrivia trivia={resource.trivia} />
  </Suspense>
</SuspenseList>

4.

该组件允许你插入任意组件或标签,并将 props 传递给它。

component:组件或标签。

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

const Dynamics = () => {
    const [selected, setSelected] = createSignal('red')

    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
    }

    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()]}/>
        </>
    )
}

export default Dynamics

5.

在目标节点处插入一个元素。

常用于 Modal 或是 Toast 弹框。

mount:挂载目标节点。

import { Portal } from 'solid-js/web'

const Portals = () => {
    return (
        <Portal mount={document.getElementById("root")}>
            <div>你好!我是Achens!</div>
        </Portal>
    )
}

export default Portals

四、全局上下文 Context与 全局状态 Store

1. Context

  • createContext:创建上下文。
  • useContext:使用上下文。
  1. Context
import { createSignal, createContext } from 'solid-js'

export const Counter = createContext([{ count: 0 }, {}])

export function Context(props){
    console.log(props)
    const [count, setCount] = createSignal({ count: props.count || 0})
    const store = [
        count,
        {
            increment(){
                console.log('--------------------------')
                setCount(c => c + 1)
            },
            decrement(){
                setCount(c => c - 1)
            }
        }
    ]

    return(
        <Counter.Provider value={store}>{props.children}</Counter.Provider>
    )
}
  1. text
// text1 ----------------------------------------
import { useContext } from 'solid-js'
import { Counter } from '../store/idnex.jsx'

const Text1 = () => {
    const [{ increment }] = useContext(Counter)

    return(
        <button onClick={increment}>+</button>
    )
}

export default Text1

// text2 ----------------------------------------
import { useContext } from 'solid-js'
import { Counter } from '../store/idnex.jsx'

const Text2 = () => {
    const [{ decrement }] = useContext(Counter)

    return(
        <button onClick={decrement}>-</button>
    )
}

export default Text2
  1. Index
import { useContext } from 'solid-js'
import { Counter } from './store/idnex.jsx'
import Text1 from './text1'
import Text2 from './text2'

const Idex = () => {
    const [count] = useContext(Counter)

    return(
        <>
            <div>count:{count()}</div>
            <Text1></Text1>
            <Text2></Text2>
        </>
    )
}

export default Idex

2. Store

1. createStore 与 Getter、produce

import { createStore, produce } from 'solid-js/store'
import { createMemo } from 'solid-js'

const Stores = () => {
    const [state, setState] = createStore({
        user: {
            userName: 'achens',
            age: 19,
            sex: '男',
            // Getter,这里使用了 Memo 优化性能
            get userInfo() {
                return createMemo(() => `${this.userName}-${this.age}-${this.sex}`)
            }
        },
        list: [
            {
                id: 0,
                color: "orange",
            },
            {
                id: 1,
                color: "blue",
            },
            {
                id: 2,
                color: "red",
            }
        ],
    })

    const updateUserName = () => {
        let round = Math.round(Math.random()*10)
        // 使用 produce 更新
        setState(produce((state) => {
            state.user.userName = `${state.user.userName}+${round}`
        }))
    }

    return(
        <>
            <div>用户名称:{state.user.userName}</div>
            <div>用户信息:{state.user.userInfo}</div>
            <button onClick={updateUserName}>更新名称</button>
            <For each={state.list}>{
                (item, index) => (
                    <li>{item.color}+{index}</li>
                )
            }</For>
        </>
    )
}

export default Stores

2. 更新 store

solid 中的 store 除去上面使用 produce 更新 state 外,还支持回调式修改与路径式修改。

一下例子都来自官网:www.solidjs.com/docs/latest…

  1. 回调式更新 store
const [state, setState] = createStore({
  firstName: "John",
  lastName: "Miller",
});

setState({ firstName: "Johnny", middleName: "Lee" });
// ({ firstName: 'Johnny', middleName: 'Lee', lastName: 'Miller' })

setState((state) => ({ preferredName: state.firstName, lastName: "Milner" }));
// ({ firstName: 'Johnny', preferredName: 'Johnny', middleName: 'Lee', lastName: 'Milner' })
  1. 路径式更新 store
const [state, setState] = createStore({
  counter: 2,
  list: [
    { id: 23, title: 'Birds' }
    { id: 27, title: 'Fish' }
  ]
});

setState('counter', c => c + 1);
setState('list', l => [...l, {id: 43, title: 'Marsupials'}]);
setState('list', 2, 'read', true);
// {
//   counter: 3,
//   list: [
//     { id: 23, title: 'Birds' }
//     { id: 27, title: 'Fish' }
//     { id: 43, title: 'Marsupials', read: true }
//   ]
// }
const [state, setState] = createStore({
  todos: [
    { task: 'Finish work', completed: false }
    { task: 'Go grocery shopping', completed: false }
    { task: 'Make dinner', completed: false }
  ]
});

setState('todos', [0, 2], 'completed', true);
// {
//   todos: [
//     { task: 'Finish work', completed: true }
//     { task: 'Go grocery shopping', completed: false }
//     { task: 'Make dinner', completed: true }
//   ]
// }

setState('todos', { from: 0, to: 1 }, 'completed', c => !c);
// {
//   todos: [
//     { task: 'Finish work', completed: false }
//     { task: 'Go grocery shopping', completed: true }
//     { task: 'Make dinner', completed: true }
//   ]
// }

setState('todos', todo => todo.completed, 'task', t => t + '!')
// {
//   todos: [
//     { task: 'Finish work', completed: false }
//     { task: 'Go grocery shopping!', completed: true }
//     { task: 'Make dinner!', completed: true }
//   ]
// }

setState('todos', {}, todo => ({ marked: true, completed: !todo.completed }))
// {
//   todos: [
//     { task: 'Finish work', completed: true, marked: true }
//     { task: 'Go grocery shopping!', completed: false, marked: true }
//     { task: 'Make dinner!', completed: false, marked: true }
//   ]
// }

五、内置方法

1. Lazy 异步组件

异步导入组件,solid 内部懒加载组件的实现方式。

import { lazy } from "solid-js";

const Lazy = () => {
  const Component = lazy(() => import("./component"));

  return (
    <div class="lazy">
      <Component></Component>
    </div>
  );
};

export default Lazy;

2. Batch 批量更新

用于将 Signal 的更改操作放到一个队列中,在某一个时期批量执行。

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

const Batch = () => {
  const [firstName, setFirstName] = createSignal("John");
  const [lastName, setLastName] = createSignal("Smith");

  const fullName = () => {
    console.log("Running FullName");
    return `${firstName()} ${lastName()}`;
  };

  const updateNames = () => {
    console.log("Button Clicked");
    batch(() => {
      setFirstName(firstName() + "n");
      setLastName(lastName() + "!");
    });
  };

  return <button onClick={updateNames}>My name is {fullName()}</button>;
};

export default Batch;

3. Untrack 取消响应式

取消 Signal 的响应性。

import { createSignal, createEffect, untrack } from "solid-js";

const Untrack = () => {
  const [a, setA] = createSignal(1);
  const [b, setB] = createSignal(1);

  createEffect(() => {
    console.log(a(), untrack(b));
  });

  return (
    <div class="untrack">
      <button onClick={() => setA(a() + 1)}>Increment A</button>
      <button onClick={() => setB(b() + 1)}>Increment B</button>
    </div>
  );
};

export default Untrack;

4. On 包装依赖

  1. 设置 defer 属性为 true 时使依赖只在第一次更改时运行。
  2. 追踪指定依赖对象。
import { createSignal, createEffect, on } from "solid-js";

const On = () => {
  const [a, setA] = createSignal(1);
  const [b, setB] = createSignal(1);

  createEffect(
    on(
      a,
      (a) => {
        console.log(a, b());
      },
      { defer: true }
    )
  );

  return (
    <div class="on">
      <button onClick={() => setA(a() + 1)}>Increment A</button>
      <button onClick={() => setB(b() + 1)}>Increment B</button>
    </div>
  );
};

export default On;

5. Transition 批量提交异步更新

用于在所有异步处理完成后在延迟提交事务中批量异步更新。这与 Suspense 有所关联,并且仅跟踪在 Suspense 边界下读取的资源

const [isPending, start] = useTransition();

// 检查是否在 transition 中
isPending();

// 包裹在 transition 中
start(() => setSignal(newValue), () => /* transition 完成 */)