This article was first published on kuusei.moe, still under construction (article content supports markdown react, richer interface styles, welcome to visit), synchronized with xLog
Background#
Since changing jobs last year, I have been using Angular for over a year. Although Angular is also very useful, it is really too restrictive. When combined with Service/Rxjs, it feels like writing JavaScript with a prefix (if you don't write something else, you won't be able to write mainstream frontend).
At the same time, my blog written with Hexo crashed due to lack of maintenance for a long time. Taking this opportunity, I decided to learn something new and get out of the big pit of Angular. Therefore, I chose a technology stack that is different from what I use at work.
A few days ago, I saw a discussion on v2ex and found that there are many new things in React. Although I have learned React for a while before, I didn't like using it because the old class components, jsx, and css in js were all ugly and had nothing to do with elegance.
But now with tailwindcss and functional components, as well as headless component libraries like radix/shadcn, they are really useful. I have also had enough of weak HTML templates like v-for/ng-for
at work. The previous disadvantages have turned into advantages or disappeared.
From the perspective of the future development trend of frontend, I think completely independent componentization is a very interesting direction. At the beginning, frontend HTML/CSS/JS were separate. I still remember when I just started learning, writing inline styles or scripts in HTML was despised (not really). Later, with the rise of frameworks like react (js+html) and vue (all in one), frontend finally had the concept of components.
Some Unpleasant Things About Angular#
Returning to the perspective of Angular, when writing Angular, although Ivy's AOT is very helpful for performance, no matter what you do, you have to register a lot of things. Let me give an example of a recent project that has been greatly simplified. When you want to create a component, what do you need to do:
-
Write a @Component annotation, in which you need to define the custom HTML element of the component, as well as the paths of the HTML and CSS files of the component. Of course, you can also write the HTML and CSS in the annotation, but then you have to endure a writing style that is even uglier than jsx---writing both HTML and CSS in strings.
-
Write a class and implement some lifecycle interfaces, and then inject services in the constructor and write a bunch of lifecycle functions. This part is relatively okay, similar to the way of writing in vue2. Since I have been writing vue2 for a long time, I am quite accepting of this set of writing styles.
-
Pass this class to the declarations and exports in the @NgModule annotation. It is worth noting that you cannot import * as in this step, so every time you register a new component, you need to configure these things.
-
When you encounter an error when registering a component in @NgModule or a compilation error, all components registered in @NgModule will report errors (as long as they have mutual dependencies), and then you will receive a screen full of errors without knowing where the error is, because all components are marked as red. Only when you know that there is an error in the component registration can you quickly locate the error.
-
If you use UI frameworks like antd, they provide capabilities such as drawers/modals for pop-up windows, and of course you will use these tools. But these tools also require registering a separate component, and then repeating the redundant work of steps 1-4.
Of course, if it is troublesome to write it purely, you can write a script and use a quick tool. Therefore, whether it is troublesome or not is not important, the problem is that it is too heavy. Whether it is registering components or services, or even writing a store, you need to register and configure a lot of things before you can start writing business logic. This is the biggest problem.
What is True Modularity#
From a design perspective, it is impossible to separate all components at the beginning of frontend development (except for basic UI buttons). Many functional components are discovered and separated during the writing process to form a component and achieve modularity.
Understanding this, let's go back to the components mentioned earlier. The components are still tightly coupled in terms of CSS, especially due to tools like scss, which makes it increasingly difficult to separate scss.
On the other hand, HTML and JS are a different story. JS and HTML are basically decoupled through modern MVVM. But when we want to turn a piece of code into a component, we need to separate the internal logic of JS, pick out the logic that belongs only to the component, and then split it.
Using react jsx, the logic and page organization are naturally associated. In general, the code at a certain level of jsx will not be called by the outer code. When splitting components, you only need to transfer the selected part and pass the external parameters as props.
Using tailwind to write CSS in tags can directly correspond the complex nesting relationship of scss to the interface. When splitting components, you also only need to transfer the code without any operation.
In summary: when splitting components, there is no need to find CSS based on HTML, or find JS based on HTML, or split JS. You can abandon the complex component registration process. You only need to do one thing—select the code you need, split it, pass props, and the componentization is complete.
Technology Stack#
Summarizing many of the above content, the overall technology stack revolves around the React ecosystem:
-
React 18
-
Next.js 13
-
Tailwind + radix/shadcn UI
-
jotai
-
swr
Some content that has not been mentioned will be discussed in detail in subsequent study notes.
Additional Content: Pitfall Record 01#
App router#
The timing of learning next.js is quite coincidental. Next 13 has just made a lot of changes, and the previous documentation is outdated. I am also studying the new set of documentation while reading the official documentation, which is like learning English.
One core change of the app router is that it has changed several ssr/ssg APIs such as getStaticPaths/getStaticProps (see file-conventions, by the way, this documentation is the first time I have seen documentation that describes project structure and file structure, what can be written in each file, and provides comprehensive examples, it is well written). getStaticPaths has been replaced by generateStaticParams, and some of the original parameters have also been changed to Route Segment Config. Then getStaticProps has been removed directly, but overall it is not a big impact. Now you can simply write a method and call it, which is actually more comfortable than before.
Deployment#
At first, I thought that the build process would be similar to other frameworks, exposing something like index.html. As a result, after the build, there were a lot of messy things in the .next folder. After checking, I found that there is an output option, which can be used to build a bunch of HTML files, and then you can deploy them with any tool.
const nextConfig = {
output: "export",
images: { unoptimized: true },
};
The current version of the code does not have SSR, it is purely static, so this setting is naturally turned on. Of course, for my SSG project, it is fine to deploy it anywhere. It is better to use Vercel's tool for deployment. It will automatically compile every time you push an update, and you don't need to set up CI by yourself.
Eslint and Automation#
Naturally, I have been using eslint and prettier for a long time, but I haven't studied the configuration files outside the src folder, which are a bunch of things.
This time, I spent a lot of time configuring eslint. Actually, it is quite simple to configure. You just need to add extends and plugin to the eslint configuration, and then adjust the rules according to your needs. After configuring it, it feels very satisfying to lint the code.
Another part is configuring prettier. I have had OCD for a while, and I used to reorder imports, but the default configuration actually only sorts alphabetically, which makes it even messier. Different dependencies are mixed together. It triggers my OCD. This time, I saw that shadcn's template used a library called prettier-plugin-sort-imports. I studied how to use it and finally I can sort dependencies according to the format I want. It feels very satisfying.
importOrder: [
"^(react/(.*)$)|^(react$)",
"^(next/(.*)$)|^(next$)",
"<THIRD_PARTY_MODULES>",
"",
"^types$",
"^@/types/(.*)$",
"^@/config/(.*)$",
"^@/lib/(.*)$",
"^@/utils/(.*)$",
"^@/hooks/(.*)$",
"^@/components/ui/(.*)$",
"^@/components/(.*)$",
"^@/styles/(.*)$",
"^@/app/(.*)$",
"",
"^[./]",
]
Finally, I also did some automation. Using husky and lint-staged, I can automatically run eslint checks and update the article's timestamp, and so on. It is also very convenient to write static blogs.
window is not defined#
@How are Client Components Rendered?
After Next 13, the tsx under the app router is completely different from before. First of all, it will pre-render the server components as class JSON files, and then display and hydrate them on the client side.
There is a misconception that when using "use client", it is easy to think that there is window and DOM throughout the execution process of the functional component, but in fact, you can only get these values when useLayoutEffect is called. Otherwise, even if you use optional chaining for window properties, it will still throw an error (I am still a bit confused here, and I will continue to study it later).
The specific rendering process will be analyzed in subsequent articles. Here, I will summarize the usage scenarios of server components and client components based on my shallow next.js experience, and update them when I have a deeper understanding in the future.
In RSC rendering, server components and client components are mixed. The usual design is:
- Write the part that retrieves data as a server component. This part of the code can even use node.js libraries such as fs path.
- Then pass the data obtained by the server component to the client component.
- Write the fixed lightweight components without complex JS operations as server components, such as buttons and links.
- Write the complex components that have a great impact on the page as client components.
- Write the places that require online data or need useEffect as client components.
- Write the data that is stored locally and determined at compile time as SSG. SSG does not affect the use of server components and client components.
One More Thing#
Finally, I would like to add a video from the React official website two years ago. Sometimes I can't help but marvel at the forward-looking architectural design, and my backwardness: -X.