携手创作,共同成长!这是我参与「掘金日新计划 · 9 月更文挑战」的第10天,点击查看活动详情 >>
官方文档:
本篇文章对应源代码:
搭建项目:
- 使用 npx 安装 solid 开发环境。
npx degit solidjs/templates/ts my-app
- 使用 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 环境
- 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,用于协调多个异步组件之间的加载,官网解释:
<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:使用上下文。
- 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>
)
}
- 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
- 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…
- 回调式更新 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' })
- 路径式更新 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 包装依赖
- 设置 defer 属性为 true 时使依赖只在第一次更改时运行。
- 追踪指定依赖对象。
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 完成 */)