add new sandpack data

This commit is contained in:
DefectingCat
2022-10-12 09:51:38 +08:00
parent 07bb550ffc
commit 30497de6c4
4 changed files with 543 additions and 1 deletions

View File

@ -9,6 +9,15 @@ import {
genericChild,
hookApp,
} from './sandpack/generic-component-encapsulate-reusable-component';
import { Button, Input } from './sandpack';
import {
store,
useDeferredValue,
useId,
useInsertionEffect,
useSyncExternalStore,
useTransition,
} from './sandpack/react18-new-hooks';
const data = {
sandpack: {
@ -19,6 +28,18 @@ const data = {
'generic-app': genericApp,
'generic-child': genericChild,
'generic-hookApp': hookApp,
common: {
Button,
Input,
},
'react18-new-hooks': {
useTransition,
useDeferredValue,
useId,
store,
useSyncExternalStore,
useInsertionEffect,
},
},
};

View File

@ -8,7 +8,7 @@ tags: [React, TypeScript]
返回一个状态值表示过渡任务的等待状态,以及一个启动该过渡任务的函数。它和 CSS 的过渡没有任何关系。useTransition 的主要目的是作用于在复杂的过渡任务时提供一个优先级较低的更新,过渡任务中触发的更新会让更紧急地更新先进行,比如点击。
过渡任务中的更新将不会展示由于再次挂起而导致降级的内容。这个机制允许用户在 React 渲染更新的时候继续与当前内容进行交互。\*\*\*\*
过渡任务中的更新将不会展示由于再次挂起而导致降级的内容。这个机制允许用户在 React 渲染更新的时候继续与当前内容进行交互。
这里渲染一个长度为 10 万的列表和两个不同的按钮,第一个按钮会增加列表的长度并使第一个状态数值加一。第二个按钮只会增加自己的状态。
@ -18,6 +18,17 @@ tags: [React, TypeScript]
`useTransition` 返回的元组中包含两个值 `[pending, setTransiton]` ,分别是 `setTransiton` 方法和表示正在过渡的状态 `pending` 。如果需要在过渡时展示特定的 UI 就可以使用 `pending` 来控制状态。
<RUASandpack
template="react-ts"
files={{
'/Button.tsx': {
code: sandpack.common.Button,
hidden: true,
},
'/App.tsx': sandpack['react18-new-hooks'].useTransition,
}}
/>
## useDeferredValue
`useDeferredValue` 接受一个状态值,并返回该的副本。返回的副本状态值将会在其他紧急更新后更新。它与 `useTransition` 比较类似,二者可以搭配使用。
@ -26,6 +37,17 @@ tags: [React, TypeScript]
将原本的状态值 `value` 与 `useDeferredValue` 返回的副本相比较就会发现 `value` 会随着 UI 一起被更新,而被延迟的状态 `deferred` 会等待 UI 更新结束后再做更新。
<RUASandpack
template="react-ts"
files={{
'/Button.tsx': {
code: sandpack.common.Button,
hidden: true,
},
'/App.tsx': sandpack['react18-new-hooks'].useDeferredValue,
}}
/>
## useId
`useId` 与其他的都不同,从名字就能看出来它的作用,返回一个在整个 APP 中唯一的 ID。它能够保证每个组件调用它返回的 ID 都唯一,即使是同一个组件被父组件多次调用。
@ -35,6 +57,17 @@ tags: [React, TypeScript]
- 为页面中需要唯一 ID 元素提供 ID例如 `<label for="ID">` 。
- SSR 到客户端注入时需要 ID 避免错误。
<RUASandpack
template="react-ts"
files={{
'/Input.tsx': {
code: sandpack.common.Input,
hidden: true,
},
'/App.tsx': sandpack['react18-new-hooks'].useId,
}}
/>
## useSyncExternalStore
`useSyncExternalStore` 是目前最适合用来取代全局状态库的 hook尤其是配合 redux 的思想后,简直就是手写的 mini redux。
@ -106,6 +139,22 @@ export default store;
其中, `listeners` 用于存放 `subscribe` 的回调,在我们更新状态后需要通知 React 来更新组件。所以在 `setState` 中遍历执行。之所以使用 `Set()` 是因为 `subscribe` 还需要返回一个函数用于注销 `listener` 。
<RUASandpack
template="react-ts"
files={{
'/Input.tsx': {
code: sandpack.common.Input,
hidden: true,
},
'/Button.tsx': {
code: sandpack.common.Button,
hidden: true,
},
'/store.ts': sandpack['react18-new-hooks'].store,
'/App.tsx': sandpack['react18-new-hooks'].useSyncExternalStore,
}}
/>
## useInsertionEffect
`useInsertionEffect` 在通常情况下都用不上,它的唯一目的是对 CSS-in-JS 库很重要。
@ -113,3 +162,10 @@ export default store;
CSS-in-JS 库通常需要在运行时插入或修改 `<style>`  标签等。当 CSS 规则被添加或删除时,浏览器必须检查这些规则是否适用于现有的 DOM 树。它必须重新计算所有的样式规则并重新应用它们--而不仅仅是改变了的那些。如果 React 发现另一个组件也产生了一个新的规则,同样的过程会再次发生。
解决这个问题的最好办法就是所有东西呈现给浏览器绘制前就进行改变。没错 `useInsertionEffect` 与 `useEffect` 有着同样的签名,但它会同步的在所有 DOM 更改之前触发。比 `useLayoutEffect` 还要早触发,这样就可以用于在重绘之前注入样式。
<RUASandpack
template="react-ts"
files={{
'/App.tsx': sandpack['react18-new-hooks'].useInsertionEffect,
}}
/>

208
data/sandpack/index.ts Normal file
View File

@ -0,0 +1,208 @@
export const Button = `type ButtonProps = {} & React.DetailedHTMLProps<
React.ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
>;
const Button = ({ ...rest }: ButtonProps) => {
const { children, className, ...props } = rest;
return (
<>
<button
className={\`rua-button \${className}\`}
{...props}
>
{children}
</button>
<style>
{\`.rua-button {
-webkit-text-size-adjust: 100%;
tab-size: 4;
box-sizing: border-box;
border-style: solid;
border-color: #e5e7eb;
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
font-family: inherit;
font-size: 100%;
font-weight: inherit;
line-height: inherit;
color: inherit;
margin: 0;
text-transform: none;
-webkit-appearance: button;
background-color: transparent;
background-image: none;
cursor: pointer;
margin-right: 1rem;
border-radius: 0.375rem;
border-width: 1px;
padding-left: 1.25rem;
padding-right: 1.25rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.rua-button:focus {
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
}
\`}
</style>
</>
);
};
export default Button;`;
export const Input = `type InputProps = {} & React.DetailedHTMLProps<
React.InputHTMLAttributes<HTMLInputElement>,
HTMLInputElement
>;
const Input = ({ ...rest }: InputProps) => {
const { className, ...props } = rest;
return (
<>
<input
className={\`rua-input \${className}\`}
{...props}
/>
<style>{\`
.rua-input {
-webkit-text-size-adjust: 100%;
tab-size: 4;
box-sizing: border-box;
border-style: solid;
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
font-family: inherit;
font-size: 100%;
font-weight: inherit;
line-height: inherit;
margin: 0;
margin-top: 0.5rem;
border-radius: 0.375rem;
border-width: 1px;
--tw-border-opacity: 1;
border-color: rgb(229 231 235 / var(--tw-border-opacity));
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
padding-top: 0.5rem;
padding-bottom: 0.5rem;
padding-left: 1rem;
padding-right: 1rem;
--tw-text-opacity: 1;
color: rgb(55 65 81 / var(--tw-text-opacity));
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.rua-input:focus {
--tw-border-opacity: 1;
border-color: rgb(96 165 250 / var(--tw-border-opacity));
outline: 2px solid transparent;
outline-offset: 2px;
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
--tw-ring-color: rgb(147 197 253 / var(--tw-ring-opacity));
--tw-ring-opacity: 0.8;
}\`}
</style>
</>
);
};
export default Input;`;

View File

@ -0,0 +1,257 @@
export const useTransition = `import Button from './Button';
import { useState, useTransition } from 'react';
const UseTransition = () => {
const [value, setValue] = useState(0);
const [value2, setValue2] = useState(-1);
const [length, setLength] = useState(30000);
const [pending, setTransiton] = useTransition();
const handleClick = () => {
setValue((v) => v + 1);
setTransiton(() => setLength((l) => l + 1));
// setLength((l) => l + 1);
};
return (
<>
<div className="my-4">
<Button onClick={handleClick}>{value}</Button>
<Button onClick={() => setValue2((v) => v - 1)}>{value2}</Button>
</div>
<div
className={\`wrapper \${pending && \`fade\`}\`}
>
{Array.from({ length }).map((_, i) => (
<div className="p-2 mb-2 mr-2 rounded-md shadow" key={length - i}>
{length - i}
</div>
))}
</div>
<style>{\`
.wrapper {
transition: all 0.3 ease;
}
.fade {
opacity: 0.5;
}\`}
</style>
</>
);
};
export default UseTransition;`;
export const useDeferredValue = `import Button from './Button';
import { useDeferredValue, useState } from 'react';
const UseDeferredValue = () => {
const [value, setValue] = useState(0);
const deferred = useDeferredValue(value);
return (
<>
<div className="my-4">
<div>
Deferred:
<Button onClick={() => setValue((v) => v + 1)}>{deferred}</Button>
</div>
<div>
Primtive:
<Button onClick={() => setValue((v) => v + 1)}>{value}</Button>
</div>
</div>
<div>
{Array.from({ length: 30000 }).map((_, i) => (
<div className="p-2 mb-2 mr-2 rounded-md shadow" key={i}>
{i}
</div>
))}
</div>
</>
);
};
export default UseDeferredValue;`;
export const useId = `import Input from './Input';
import { useId } from 'react';
const RUAForm = () => {
const id = useId();
return (
<>
<label htmlFor={\`\${id}\`}>Label 1: </label>
<div>
<Input type="text" id={\`\${id}\`} />
</div>
</>
);
};
const UseId = () => {
return (
<>
<RUAForm />
<RUAForm />
</>
);
};
export default UseId;`;
export const store = `export type State = {
count: number;
info: string;
};
export type Store = {
state: State;
setState: (
stateOrFn: Partial<State> | ((state: State) => Partial<State>)
) => void;
subscribe: (listener: () => void) => () => void;
listeners: Set<() => void>;
getSnapshot: () => State;
};
const store: Store = {
state: {
count: 0,
info: 'Hello',
},
setState(stateOrFn) {
const newState =
typeof stateOrFn === 'function' ? stateOrFn(store.state) : stateOrFn;
store.state = {
...store.state,
...newState,
};
store.listeners.forEach((listener) => listener());
},
listeners: new Set(),
subscribe(listener) {
store.listeners.add(listener);
return () => {
store.listeners.delete(listener);
};
},
getSnapshot() {
return store.state;
},
};
export default store;`;
export const useSyncExternalStore = `import Button from './Button';
import Input from './Input';
import { useSyncExternalStore } from 'react';
import store from './store';
const Couter = () => {
const { count, info } = useSyncExternalStore(
store.subscribe,
store.getSnapshot
);
return (
<>
<div>
<div>
Count: <span>{count}</span>
</div>
<div>
Info: <span>{info}</span>
</div>
<div>
<Button
onClick={() => store.setState((d) => ({ count: d.count + 1 }))}
>
Add
</Button>
</div>
</div>
</>
);
};
const Infor = () => {
const { count, info } = useSyncExternalStore(
store.subscribe,
store.getSnapshot
);
return (
<>
<div>
<div>
Count: <span>{count}</span>
</div>
<div>
Info: <span>{info}</span>
</div>
<div>
<Input
type="text"
onChange={(e) => store.setState({ info: e.target.value })}
/>
</div>
</div>
</>
);
};
const UseSyncExternalStore = () => {
return (
<>
<Couter />
<hr className="my-4" />
<Infor />
</>
);
};
export default UseSyncExternalStore;`;
export const useInsertionEffect = `import { useEffect, useLayoutEffect, useInsertionEffect } from 'react';
const Child = () => {
useEffect(() => {
console.log('useEffect child is called');
});
useLayoutEffect(() => {
console.log('useLayoutEffect child is called');
});
useInsertionEffect(() => {
console.log('useInsertionEffect child is called');
});
return <></>;
};
const UseInsertionEffect = () => {
useEffect(() => {
console.log('useEffect app is called');
});
useLayoutEffect(() => {
console.log('useLayoutEffect app is called');
});
useInsertionEffect(() => {
console.log('useInsertionEffect app is called');
});
return (
<>
<Child />
<div>Check console in DevTools</div>
</>
);
};
export default UseInsertionEffect;`;