SolidJS is a front-end framework with a syntax like React Function Component and a core like Vue. This week we read the article Introduction to SolidJS to understand its core concepts.
Why introduce SolidJS instead of other front-end frameworks? Because SolidJS is teaching the React team to implement Hooks correctly, which is very rare in an era when only the concept of React and the concept of virtual DOM are following the lead. This is also the charm of open source technology: any point of view can be challenged freely, as long as you are right, you may stand out. .
Overview
The entire article explains the usage of SolidJS from a newcomer's perspective, but this article assumes that the reader has a React foundation, so just explain the core differences.
The render function is executed only once
SolidJS only supports the writing method of FunctionComponent, no matter whether the content has state management or not, and whether or not the component accepts the pass-through of Props from the parent component, the rendering function is only triggered once.
So its state update mechanism is fundamentally different from React:
- After the React state changes, respond to the state change by re-executing the Render function body.
- After the Solid state changes, it responds to the state change by re-executing the block of code that uses that state.
Compared with the re-execution of the entire rendering function of React, the solid state response granularity is very fine, and even if multiple variables are called within a JSX, the entire JSX logic will not be re-executed, but only the variable part will be updated:
const App = ({ var1, var2 }) => (
<>
var1: {console.log("var1", var1)}
var2: {console.log("var2", var2)}
</>
);
The above code only prints var1
when var1
is changed alone, but not var2
, which is impossible in React.
All of this stems from the core idea of SolidJS's challenge to React: state-oriented rather than view-oriented . Because of this difference, the rendering function is executed only once, and by the way, the result of variable update granularity is so fine, which is also the basis for its high performance, and also solves the stubborn problem that React Hooks is not intuitive enough.
Better Hooks implementation
SolidJS uses createSignal
to achieve the ability similar to React useState
, although it looks similar, but the implementation principle and the mind when using it are completely different:
const App = () => {
const [count, setCount] = createSignal(0);
return <button onClick={() => setCount(count() + 1)}>{count()}</button>;
};
We're going to understand this code entirely in the SolidJS mind, not the React mind, even though it looks a lot like Hooks. One notable difference is that mentioning the status code to the outer layer also works perfectly:
const [count, setCount] = createSignal(0);
const App = () => {
return <button onClick={() => setCount(count() + 1)}>{count()}</button>;
};
This is the fastest way to understand the concept of SolidJS, that is, SolidJS does not understand the concept of React at all. The data-driven understanding of SolidJS is a pure data-driven view. No matter where the data is defined or where the view is, binding can be established.
Naturally, this design does not depend on the rendering function being executed multiple times. At the same time, because of the use of dependency collection, there is no need to manually declare the deps array. It is also possible to write createSignal
after the conditional branch, because there is no execution order. concept.
Derived state
Declare the derived state with a callback function:
const App = () => {
const [count, setCount] = createSignal(0);
const doubleCount = () => count() * 2;
return <button onClick={() => setCount(count() + 1)}>{doubleCount()}</button>;
};
This is a less convenient point than React, which pays a huge price (re-executing the entire function body after data changes), so the derived state can be defined in a simpler way:
// React
const App = () => {
const [count, setCount] = useState(0);
const doubleCount = count * 2; // 这块反而比 SolidJS 定义的简单
return (
<button onClick={() => setCount((count) => count + 1)}>
{doubleCount}
</button>
);
};
Of course, the author does not respect the derivative writing of React, because it is too expensive. We continue to analyze why the seemingly simple way of writing derived state in SolidJS works. The reason is that SolidJS collects all the dependencies that use count()
, and doubleCount()
uses it, and the rendering function uses doubleCount()
to hang naturally, so that's it. On the dependencies, the implementation process is simple and stable, without Magic.
SolidJS also supports derived field calculation cache, using createMemo
:
const App = () => {
const [count, setCount] = createSignal(0);
const doubleCount = () => createMemo(() => count() * 2);
return <button onClick={() => setCount(count() + 1)}>{doubleCount()}</button>;
};
There is also no need to write the deps dependency array. SolidJS is driven by dependency collection count
changes affect doubleCount
this step, so that when accessing doubleCount()
it is not necessary to always execute its callback function body, resulting in additional performance overhead.
Status monitoring
Benchmarking React's useEffect
, SolidJS provides createEffect
, but in contrast, without writing deps, it is really monitoring data, not a part of the component life cycle:
const App = () => {
const [count, setCount] = createSignal(0);
createEffect(() => {
console.log(count()); // 在 count 变化时重新执行
});
};
This again shows why SolidJS is qualified to "teach" the React team to implement Hooks:
- No deps statement.
- Separating listening from lifecycle, React often confuses them easily.
In SolidJS, the lifecycle functions are onMount
, onCleanUp
, and the status monitoring functions are createEffect
; and all the lifecycle and status monitoring functions of React are useEffect
, although it looks more concise, it is not easy for even a veteran of React Hooks to judge which are listeners and which are lifecycles.
Template compilation
Why can SolidJS solve so many historical problems of React so magically, but React can't? The core reason is the template compilation process added by SolidJS.
Take the Demo provided by the official Playground as an example:
function Counter() {
const [count, setCount] = createSignal(0);
const increment = () => setCount(count() + 1);
return (
<button type="button" onClick={increment}>
{count()}
</button>
);
}
is compiled as:
const _tmpl$ = /*#__PURE__*/ template(`<button type="button"></button>`, 2);
function Counter() {
const [count, setCount] = createSignal(0);
const increment = () => setCount(count() + 1);
return (() => {
const _el$ = _tmpl$.cloneNode(true);
_el$.$$click = increment;
insert(_el$, count);
return _el$;
})();
}
First extract the component JSX part to the global template. Initialization logic: insert variables into the template; update state logic: because insert(_el$, count)
has been bound count
and _el$
, the next call setCount()
, just update the bound _el$
, and don't care where it is.
In order to implement this function more completely, the Node that uses the template must be completely separated. We can test slightly more complex scenarios like:
<button>
count: {count()}, count+1: {count() + 1}
</button>
The template result after this code compiles is:
const _el$ = _tmpl$.cloneNode(true),
_el$2 = _el$.firstChild,
_el$4 = _el$2.nextSibling;
_el$4.nextSibling;
_el$.$$click = increment;
insert(_el$, count, _el$4);
insert(_el$, () => count() + 1, null);
The template is divided into a whole and three sub-blocks, namely literals, variables, and literals. Why is the last variable not added? Because the last variable insertion is directly placed at the end of _el$
, and the middle insertion position needs to be insert(_el$, count, _el$4)
to give instances of parent and child nodes.
intensive reading
The mystery of SolidJS has been solved, and the author asks and answers some questions by himself.
Why doesn't createSignal have order restrictions like hooks?
React Hooks uses deps to collect dependencies. When the rendering function body is executed next time, because there is no way to identify "for which Hook deps is declared", it can only rely on the order as the basis for identification, so a stable order is required, so conditional branches cannot occur in Front.
The rendering function of SolidJS itself is only executed once, so there is no scene where React re-executes the function body, and createSignal
itself just creates a variable, createEffect
also just creates a monitor, the logic is all It is handled inside the callback function, and the binding to the view is done through dependency collection, so it is not affected by conditional branches.
Why doesn't createEffect have the useEffect closure problem?
Because the SolidJS function body is executed only once, there is no situation where there are N closures in the component instance, so there is no closure problem.
Why is React a fake responsive?
React responds to changes in the component tree and updates responsively through top-down rendering of the component tree. And SolidJS only responds with data, even if the data definition is declared outside the rendering function.
Therefore, although React says that it is responsive, what developers really respond to is the layer-by-layer update of the UI tree. In this process, there will be closure problems, manual maintenance of deps, hooks cannot be written after conditional branches, and sometimes split It is unclear whether the current update is caused by the parent component renderer or because of state changes.
All this shows that React does not let developers really only care about data changes. If they only care about data changes, why is the reason for component re-rendering maybe because of "parent component rerender"?
Why SolidJS removed virtual dom still fast?
Although virtual dom avoids the performance loss of the overall refresh of dom, it also brings diff overhead. For SolidJS, it asks a question: why avoid the overall refresh of the dom, and not the partial update?
Yes, local update is not impossible. After template rendering, the dynamic part of jsx is extracted separately, and with dependency collection, point-to-point update can be achieved when variables change, so there is no need to perform dom diff.
Why can't the signal variable use count()
be written as count
?
The author has not found the answer. In theory, Proxy should be able to complete this explicit function call action, unless you don't want to introduce the development habit of Mutable and make the development habit more Immutable.
Binding of props does not support destructuring
Due to the reactive nature, destructuring loses the properties of the proxy:
// ✅
const App = (props) => <div>{props.userName}</div>;
// ❎
const App = ({ userName }) => <div>{userName}</div>;
Although splitProps
is also provided to solve the problem, this function is still unnatural. A better solution to this problem is to avoid it through the babel plugin.
createEffect does not support async
Although it is very convenient without deps, it is still unsolvable in asynchronous scenarios:
const App = () => {
const [count, setCount] = createSignal(0);
createEffect(() => {
async function run() {
await wait(1000);
console.log(count()); // 不会触发
}
run();
});
};
Summarize
There is only one core design of SolidJS, which is to make data-driven really return to the data, rather than being tied to the UI tree, and at this point, React goes astray.
Although SolidJS is great, the ecosystem of related components has not yet developed, and the huge migration cost is the biggest problem that it is difficult to quickly replace it to the production environment. The front-end ecosystem wants to be seamlessly upgraded. It seems that the first step is to think about the "code paradigm" and how to convert between code paradigms. After the paradigm is determined, it will be implemented by community competition, so that the problem of difficult ecological migration will not be encountered. .
But the above assumptions are not true, technical iteration will always be at the expense of BreakChange, and many times we can only abandon old projects and practice new technologies in new projects, just like the Jquery era.
The discussion address is: Intensive Reading "SolidJS" Issue #438 dt-fe/weekly
If you'd like to join the discussion, click here , there are new topics every week, with a weekend or Monday release. Front-end intensive reading - help you filter reliable content.
Follow Front-end Intensive Reading WeChat Official Account
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
Copyright notice: Free to reprint - non-commercial - non-derivative - keep attribution ( Creative Commons 3.0 license )
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。