我们都知道 SolidJS ,如果不知道,那你为啥要看这篇文章 ( ̄▽ ̄)"
我们都知道 SolidJS ,这是它的文档: https://www.solidjs.com/
简单来说,SolidJS 就是真正 "react" 版的 React ,完全按需更新(哪里不懂点哪里),哪里数据变化更新哪里。
比如一个组件:
function App() {
const [value, setValue] = useState(0);
return <div>{value}</div>;
}
React 是把 App
整个函数死去活来的调用(即 re-render ),而 SolidJS 则只更新 value
那一小块。
当然,SolidJS 中是这么写的:
function App() {
const [value, setValue] = createSignal(0);
return <div>{value()}</div>;
}
在 SolidJS 中, App
只在初始化时被调用一次,之后就不再执行。
所以 SolidJS 里的 JSX 相当于 "静态模板",只用来描述 UI 而不会再次调用,更没有 diff 。
也就是说,随便在 App
里执行函数,随便在 JSX 里执行函数,都只触发一次。
如何把 React 变为 SolidJS ?
当然不是把 solid-js
重命名 react
,也不是手动操作 DOM 脱离 React 的逻辑去更新。
这里必须声明一下:
以下实现,完全是基于 React 的 API 做的,而不是用 DOM API 或 jQuery 来 hack 实现的,那样的话就失去了意义。
value()
一小块?这里是实现思路的核心所在,直接说了吧 —— 就是把 value()
变成一个组件。
是的,它展示的是数据,但它其实是一个组件。它是一个只返回数据的组件。
value()
而不是 value
?因为需要知道这里有一个数据,后续得更新,怎么知道呢?
根据 JS 语法,除了 state.value
(监听 getter
)或 value()
(调用函数),别无他法。
这也是为什么 SolidJS 必须写成 value()
,如果写成 value
,神仙也不知道怎么去更新这个数据,因为在 "静态模板" 的实现方式下,函数不会再次运行。
createSignal
的 useSignal
我们希望实现一个 useSignal
,类似 SolidJS 的 createSignal
,返回 getter 和 setter 两个函数。
同时,getter 的返回得是一个组件。
function useSignal(val) {
const valRef = useRef(val);
const update = useRef();
const Render = () => {
const [value, setValue] = useState(valRef.current);
update.current = setValue;
return value;
};
const getter = () => {
try {
useState(); // 通过此 hack 获知数据是在 JSX 中,还是其它位置正常读取
return <Render />;
} catch (e) {
return valRef.current;
}
};
const setter = (newVal) => {
valRef.current = newVal;
update.current(newVal);
};
return [getter, setter];
}
上面是一个极简实现,但有问题,因为数据可能用在多处,而上文只能更新最后一处的数据。
useSignal
用一个 listeners
数组将更新函数们收集起来,这样就行了。其实这也是 React 状态管理器们的实现思路。
function useSignal(val) {
const valRef = useRef(val);
const listeners = useRef([]);
const Render = () => {
const [value, setValue] = useState(valRef.current);
useEffect(() => {
listeners.current.push(setValue);
return () => {
listeners.current.splice(listeners.current.indexOf(setValue), 1);
};
}, []);
return value;
};
return [
() => {
try {
useState();
return <Render />;
} catch (e) {
return valRef.current;
}
},
(payload) => {
listeners.current.forEach((listener) => {
listener((prev) => {
valRef.current =
typeof payload === 'function' ? payload(prev) : payload;
return valRef.current;
});
});
},
];
}
上面已经是一个可用的实现。
写到这里,其实故事的核心已经讲完了。
但如果要真用于开发需求,还有很多未尽的事业。
如果要真的 "可用",最起码还应该实现:
上面写了一堆问题,自然是准备好了答案 ... 这个答案就叫 solid-react
。
上文提到的所有问题都解决了,若深入了解的话可以看源码。
☞ GitHub: https://github.com/nanxiaobei/solid-react
下面是 solid-react
的 API:
Run(() => fn(value())
)请注意 API 的命名,这也是有说法的:尽量不与已有 API 冲突(比如不直接命名为 useState
useMemo
,那样的话会让代码一团迷惑),同时还得保持足够的简洁(写起来不费事)和直观(理解起来不费事)。
具体 API 介绍请查看 README: https://github.com/nanxiaobei/solid-react 。
如此,已经可以覆盖大多数常见的开发场景,也就是可用于 "生产" 了。
Demo: https://codesandbox.io/s/solid-react-rymhr6?fontsize=14&hidenavigation=1&theme=dark&file=/src/App.js
这里是一个 demo ,可以打开 console ,点击按钮试试,然后你就会发现:
组件再也不 re-render 了,React 完全变成了 SolidJS 式的按需更新!
useUpdate
useAuto
也不需要 deps
之类的玩意,对其依赖都是自动获知。而且只有在依赖变化时,它们才再次执行。
是的,也就是可以摆脱 Hooks 了,什么 useCallback
useMemo
deps
memo
,会不会触发 re-render ,通通不需要了。
函数就是函数,对象就是对象,写在那里就不会再次新建了,完全不需要再包一层,随便给子组件 props 传递吧。
solid-react
是一个实验项目,只是为了实现一个想法,而事实上实现的还不错。
solid-react
尽力做成了 "能力完备",无论是发请求,还是监听数据,麻雀虽小(但很好吃),五脏俱全。
solid-react
是一个小东西,它可能会有缺陷,当然不能跟直接用 React 开发的成熟度对比,也不可能比。
solid-react
用于小 demo 项目肯定没问题,但未在大项目中实践过,它适合先用来玩一下,如果感兴趣的话。
solid-react
更像是一种理念,React 官方不可能走上这条路,但感谢开源,大可以自己在这条路上实验一下。
solid-react
为 "饱受 Hooks 写法困扰" 这一好几年也没消失的业界普遍困惑而努力(虽然我自己感觉 Hooks 还好)
solid-react
欢迎感兴趣的人一起尝试,创造出更多可能。
将 React 变成 SolidJS ,告别 Hooks ,告别 re-render ↓↓↓
https://github.com/nanxiaobei/solid-react
肯定有人想说,上面 solid-react 的 API 不就是 Hooks 吗?怎么告别 Hooks !其实上面是为了兼容 React 和 solid-react 混用的情况 ... 是的,我连这种情况都考虑到了 🙈
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.