In a front end interview, there are a lot of questions. Framing is almost one of the questions to ask. As one of the most popular SPA frameworks, Vue is the most important part of the interview process. As an extremely important role in Vue ecology, Vue Router is also a skill we must master.
This article will introduce the use of the Vue Router and implement a simple version of the Vue Router.
Vue Router Basic review
Using the step
To review the use of the VUE Router, first create a VUE project using the Vue CLI.
Install the VUE CLI globally.
npm i -g @vue/cli
Copy the code
After the installation, check whether the version is normal.
vue --version
Copy the code
Then create a demo project.
vue create vue-router-demo
Copy the code
Start with a custom selection. Manually select the features.
Vue CLI will ask some questions. You only need to select Babel, Router, and Linter.
The Vue CLI will help us create the basic code structure of the VUE Router.
Enter the project and start the project.
npm run serve
Copy the code
You can then see the effect of the route in your browser.
The steps to use the VUE Router in vUE are as follows.
- Routing page
Create the page corresponding to the route.
The default is in the Views folder.
- Registering routing plug-ins
Use vue.use (VueRouter) to register the routing plug-in. The vue. use method is used specifically to register plug-ins and is called directly if a function is passed in. If an object is passed in, the object’s install method is called.
- Creating a Route Object
A routing rule is an array of objects, each of which is a rule. Objects have properties such as Path and Component, where path stands for path and compoent stands for the rendered component. When the path changes in the browser, the corresponding component is rendered into the page.
Create an object by means of new VueRouter. The constructor of VueRouter is an object. Set the routes property of this object to the routing rule just defined.
The default is in router/index.js.
- Registering a Router Object
In new Vue, the Router option in the configuration object is set to the route object created above.
- Create a routing component placeholder
Use the router-view label to create a placeholder for the routing component in the element corresponding to the EL option specified by the Vue instance. The routing component will render to this location every time.
- Create links
Router-link is used to create links and to change routes.
When the router option is enabled for a Vue instance, the instance object has two additional attributes, $route and $router.
$route is the current routing rule object, which stores information such as paths and parameters.
$router is a route instance object that stores many routing methods, such as push, replace, go, and so on. It also stores routing information, such as mode and currentRoute.
Dynamic routing
Suppose you have a user details page. We won’t create a detail page for every user, because the page is generic and only the user’S ID changes.
First add a route.
The path before the: ID is fixed. The: ID itself means to receive an ID parameter.
Component returns a function. This is how route lazy loading is written.
When this route is triggered, the component is rendered. When this route is not triggered, the component is not rendered. You can improve performance.
// ... other code
const routes = [
// ... other code
{
path: "/detail/:id". name: "Detail". component: (a)= > import(".. /views/Detail.vue"), }, ]; Copy the code
Once you have the route, create a user page.
<template>
<div>User ID: {{$route.params.id}}</div>
</template>
<script> export default { name: "Detail".}; </script> Copy the code
This is the first way to get the parameters of a dynamic route, through the routing rules, to get the parameters.
One drawback to this approach, however, is that it forces a dependency on $route to work.
This dependency can be reduced in another way.
Example Enable the props attribute for a route rule.
// ... other code
const routes = [
// ... other code
{
path: "/detail/:id". name: "Detail". props: true. component: (a)= > import(".. /views/Detail.vue"), }, ]; Copy the code
The props attribute is used to pass the parameters of the route as props to the component. The component can then retrieve the parameters using props.
<template>
<div>Current user ID: {{ID}}</div>
</template>
<script> export default { name: "Detail". props: ["id"].}; </script> Copy the code
So the Detail component doesn’t have to be in the route, just pass an ID attribute, and it can be applied anywhere.
Therefore, it is more recommended to use props to pass route parameters.
Embedded routines by
When multiple routing components have the same content, the same content of multiple routing components can be extracted into a common component.
Assume that the home and detail pages have the same head and tail. You can extract a Layout component, extract the header and tail into the Layout component, and place a Router View in the changed location. When the corresponding route is accessed, the contents of the routing component and the Layout component are merged into the output.
Suppose you have a landing page that doesn’t need a layout, so it doesn’t need to be nested.
Write the Layout component.
<template>
<div>
<div>
<header>header</header>
</div>
<div> <router-view /> </div> <div> <footer>footer</footer> </div> </div> </template> <script> export default { name: "login".}; </script> <style> header { width: 100%; background: #65b687; color: #39495c; } footer { width: 100%; background: #39495c; color: #65b687; } </style> Copy the code
Create the login. vue component.
<template>
<div>Landing page</div>
</template>
<script> export default { name: "login".}; </script> Copy the code
Modify the contents of the template block in app.vue.
<template>
<div id="app">
<router-view />
</div>
</template>
Copy the code
Modify the routes configuration.
const routes = [
{
path: "/login". name: "login". component: Login,
}, { path: "/". component: Layout, children: [ { name: "home". path: "". component: Home, }, { name: "detail". path: "detail:id". props: true. component: (a)= > import(".. /views/Detail.vue"), }, ]. }, ]; Copy the code
When visiting http://localhost:8080/login, will be normal to enter the Login module.
When you access http://localhost:8080/, the Layout component for/is loaded first, then the Home component is loaded, and the contents of the Layout component and the Home component are merged.
Visit http://localhost:8080/detail/id, will and loading ways of the Home, loading Layout first, then load the Detail, and the id passed in. Finally, merge the contents of the two components.
Programmatic navigation
In addition to using router-link for navigation, we can also use JS code for navigation.
This is a very common requirement, such as clicking a button, making a logical decision and then navigating.
There are four common programmatic navigation apis. $router.push, $router.replace, $router.back and $router.go
Modify the above three pages to experience the four apis.
Landing page Go to the home page by clicking the landing button.
<template>
<div>
<div>Landing page</div>
<button @click="push">landing</button>
</div>
</template> <script> export default { name: "login". methods: { push() { this.$router.push("/"); // this.$router.push({ name: 'home' }) }, }, }; </script> <style> button { background: #39495c; color: #65b687; border-radius: 8px; padding: 5px 10px; border: none; outline: none; } </style> Copy the code
The home page can jump to the user details page or exit to the login page, and the current page is not saved in the browser’s browsing history.
<template>
<div class="home">
<div>Home Page.</div>
<button @click="goToDetail">View the profile of user 8</button>
<button @click="exit">exit</button>
</div> </template> <script> export default { name: "Home". methods: { goToDetail() { this.$router.push("/detail/8"); // this.$router.push({ name: 'detail', params: { id: 8 } }) }, exit() { // this.$router.replace('/login') this.$router.replace({ name: "login" }); }, }, }; </script> Copy the code
You can roll back to the previous page or two pages in the user details page.
<template>
<div>
<div>Current user ID: {{ID}}</div>
<button @click="back">return</button>
<button @click="backTwo">The back page or two</button>
</div> </template> <script> export default { name: "Detail". props: ["id"]. methods: { back() { this.$router.back(); }, backTwo() { this.$router.go(2 -); }, }, }; </script> Copy the code
The use of the push and replace methods is basically the same; you can either pass a string or an object to navigate the page. If a string is passed, it represents the path to the page. If you pass an object, the name attribute of the object is used to find the corresponding page component. If you need to pass arguments, you can concatenate strings or set params properties in the object. The difference is that the replace method does not record the browsing history of the current page in the browser, whereas the push method does.
The back method, which goes back to the previous page, is the simplest to use without passing arguments.
The go method can pass a parameter of type number, indicating whether to go forward or backward. A negative number means going back, a positive number means going forward, and a 0 will refresh the current page.
Hash mode and History mode
The Vue Router has two types of routing modes: hash mode and History mode. The hash mode has a hash sign (#) in the navigation bar address, while the History mode does not.
Both modes are handled by the client, using JavaScript to listen for routing changes and render different content based on different urls. If you need server-side content, use Ajax to get it.
The difference of expression form
Aesthetically, the History mode is more aesthetically pleasing.
Hash mode URL. It is accompanied by a hash sign (#) and a question mark (?) if passing arguments. .
http://localhost:8080/#/user? id=15753140
Copy the code
Link to the History mode.
http://localhost:8080/user/15753140
Copy the code
However, history cannot be used directly and needs to be supported by server configuration.
Differences in principles
Hash patterns are based on anchor points and onHashChange events.
The History mode is based on the History API in HTML5. The History object has pushState and replaceState methods. However, it is important to note that the pushState method is not supported until IE10. Before IE10, only Hash mode can be used.
The History object also has a push method that changes the address of the navigation bar and sends a request to the server. The pushState method can just change the navigation bar address without sending a request to the server.
The History mode
History requires server support.
The reason is that in single-page applications, there is only one index.html. But in a single page application by clicking on the normal into http://localhost:8080/login will not have a problem. But when the browser is refreshed, the server is requested, and no resource for this URL exists on the server, and a 404 is returned.
So you should configure the server to return index.html for all requests except for static resources.
Let’s show you what happens when pages don’t match.
Create 404.vue in the views directory.
<template>
<div class="about">
<h1>404</h1>
</div>
</template>
Copy the code
Add route 404 in routes.
const routes = [
// other code
{
path: "*". name: "404". component: (a)= > import(".. /views/404.vue"), }, ]; Copy the code
Add a link to home.vue that does not exist.
<router-link to="/video">video</router-link>
Copy the code
Then start the server, go to the home page, and click the video link, which will take you to the 404 page.
This is an effect we expect to have. The default server of Vue CLI has been configured for us. But when we actually deploy, we still need to configure the server ourselves.
Node.js server configuration
Start by developing a server using NodeJS.
Create a server folder and initialize the project.
npm init -y
Copy the code
Install project dependencies, here using Express and connect-history-api-fallback.
Express is a well-known NodeJS Web development server framework.
Connect-history-api-fallback is a module that handles the history mode.
npm i express connect-history-api-fallback
Copy the code
Create and write the server.js file.
const path = require("path");
// The module that handles the history mode
const history = require("connect-history-api-fallback");
const express = require("express");
const app = express(); // Register the middleware that handles the History pattern app.use(history()); // Register the middleware that handles static resources app.use(express.static(path.join(__dirname, "./web"))); app.listen(4000, () = > { console.log(` App running at: - Local: http://localhost:4000/ `); }); Copy the code
The web folder in the root directory of the Server project is set as the root path of the website.
When server.js is started, the URL requesting http://localhost:4000/ will go to the web folder to find the corresponding resource.
Now package the original Vue project.
Back in the vue project, run the package command.
npm run build
Copy the code
You can get the dist folder.
The project is deployed by copying everything in the dist directory into the Web directory of the Server project.
Next, run server.js.
node server.js
Copy the code
Open a browser, enter the detail page (http://localhost:4000/detail/8). Refresh the browser and everything is fine.
If you don’t deal with history, you have a problem.
Try to comment out app.use(history()) and restart the server.
Again, go to the Detail page, refresh your browser, and you’ll get to the default Express 404 page. And the reason for that is that refreshing the browser calls on the server. The server could not find the detail/8 resource in the Web directory. If history processing is enabled and the server cannot find detail/8, it returns index.html and the client renders the component based on the current path.
Nginx server configuration
First install nginx.
You can download the nginx package from the nginx official website.
http://nginx.org/en/download.html
Decompress the package to a directory that does not contain Chinese.
Or install it with some tool, such as BREW.
brew install nginx
Copy the code
The nginx command is simple. The common commands are as follows:
Start the
nginx
Copy the code
restart
nginx -s reload
Copy the code
stop
nginx -s stop
Copy the code
The default port of nginx is 80. If 80 is not in use, nginx starts normally. After the startup, go to http://localhost.
The default port of nGINx installed on BREW is 8080.
Copy the contents of the Vue project dist folder to the HTML folder in the nginx folder. The HTML folder is the default nginx folder.
After a successful deployment, you access the project in your browser and find the same refresh 404 problem.
In this case, you need to add the corresponding configuration to the nginx configuration file.
The default configuration for nginx is in conf/nginx.conf.
In nginx.conf, locate the server module that listens to 80, and locate the location/in that module.
Add the try_files configuration.
location / {
root html;
index index.html index.htm;
# $uri 是 nginx 的变量,就是当前这次请求的路径
# try files 会尝试在这个路径下寻找资源,如果找不到,会继续朝下一个寻找
# $uri/ 的意思是在路径目录下寻找 index.html 或 index.htm
# 最后都找不到的话,返回 index.html
try_files $uri $uri/ /index.html;
}
Copy the code
After modifying the configuration file, nginx needs to restart.
nginx -s reload
Copy the code
After the restart in the browser operation, everything is normal.
Simulate the Vue Router
Since the implementation of history and hash patterns is similar, the history pattern is directly used for emulation.
Implementation Principles Review
Now let’s review how the Vue Router works again.
The vue router is a front-end route. When a path is changed, the browser determines the current path and loads components corresponding to the current path.
Hash mode:
- Whatever follows the # in the URL is the path address
- Listen for hashChange events
- Find the corresponding component to re-render according to the current routing address
The history mode:
- Change the address bar with the history.pushState() method
- Listen for popstate events
- Find the corresponding component to re-render according to the current routing address
Analysis of the
By observing the use of the VUE Router, you can quickly infer how the VUE router is implemented.
Here is a simple usage process.
// Register the plug-in
Vue.use(VueRouter);
// Create a route object
const router = new VueRouter({
routes: [{ name: "home".path: "/".component: homeComponent }],
}); // Create a Vue instance and register the Router object new Vue({ router, render: (h) = > h(App), }).$mount("#app"); Copy the code
The first is to execute vue.use to register VueRouter.
The vue.use method is used to register plug-ins. Vue is powerful because of its plug-in mechanism. Components like VueRouter, Vuex, and others are implemented using the plug-in mechanism.
The vue. use method can take a function or an object. If it is a function, it calls the function directly, or if it is an object, it calls the install method on the object. The VueRouter here is an object.
Next, a router instance is created, so VueRouter should be either a constructor or a class.
In combination with the above analysis, we can see that VueRouter is a class with the install method.
The VueRouter constructor is an object, and the constructor object has a routes attribute that records the configuration of the route.
Finally, the Router object is passed into the constructor parameter object that creates the Vue instance.
The class VueRouter can be described using a UML class diagram.
The UML class diagram consists of three parts.
The top part is the name of the class, the second part is the instance properties of the class, and the third part is the methods of the class, where the plus sign (+) represents the prototype method and the underscore (_) represents the static method.
attribute
- Options: Object used to store the objects passed in the constructor.
- Data: object with the current property that records the address of the current route. This object is reactive.
- RouteMap: Object that records the mapping between routing addresses and components.
methods
- Constructor: constructor.
- Install: A static method agreed by the Vue plug-in mechanism.
- Init: Initialization function used to combine initRouteMap, initComponents, and initEvent.
- InitRouteMap: Parses routes in Options and sets the rules to the routeMap.
- InitComponents: creates router-link and router-view components.
- InitEvent: listens for changes in data.current and switches views.
install
Create a new project using the Vue CLI and select Babel, Vue Router, esLint from the configuration options for our tests.
When using vue.use (), install is called first, so implement install first.
First, analyze what a few things are implemented in install.
- Vue has the plug-in installed. If so, there is no need to repeat the installation.
- Store the Vue constructor in a global variable. This is because VueRouter’s instance methods will use the Vue constructor’s methods later in the VueRouter instance methods. For example, to create a router-link, router-view, or other components, you need to call Vue.components.
- Inject the VueRouter instance object passed in when creating the Vue instance into all Vue instances. This.$router is injected into the Vue instance at this point.
Create the vue-router directory under the SRC directory and create the index.js file in it.
let _Vue = null;
export default class VueRouter {
static install(Vue) {
// 1. Check whether the plug-in is installed
if (VueRouter.install.installed) { return; } VueRouter.install.installed = true; // 2. Store the Vue constructor in a global variable _Vue = Vue; // 3. Inject the router object passed in when creating Vue instances to all Vue instances / / with _Vue.mixin({ beforeCreate() { if (this.$options.router) { _Vue.prototype.$router = this.$options.router; } }, }); } } Copy the code
The first step is simply to record whether or not a plug-in has been installed. A better approach is to add an installed property to the install method of the plug-in itself, rather than a global variable. If so, return directly. No, set Installed to true and continue executing the logic.
The second step is as simple as assigning the global _Vue value.
The third step is harder because we don’t know when new Vue will be called, so we don’t get the Router object in the construct parameter. The problem can be solved by blending in. The object passed in the mixin method has the beforeCreate method, which is the hook function for new Vue. This in this function refers to the Vue instance, so you can inject the VueRouter instance into all Vue instances. Since each component is also a Vue instance, it is necessary to distinguish between a Vue instance and a component, otherwise the logic of the prototype extension will be executed many times. If this.$options has router properties, only Vue instances have router properties. Components do not.
The constructor
Next, implement the constructor, whose logic is simpler.
Three instance properties are created. Options is used to store construction parameters; A routerMap is a key-value pair object. The property name is the route address, and the property value is the component. Data is a reactive object with a current property that records the current routing address. Reactive objects can be created using _vue.Observable.
export default class VueRouter {
// other code
constructor(options) {
this.options = options;
this.routeMap = {};
this.data = _Vue.observable({ current: "/". }); } } Copy the code
initRouteMap
This function converts the routes properties in the constructor parameter options into key-value pairs and stores them on the routeMap.
export default class VueRouter {
// other code
initRouteMap() {
this.options.routes.forEach((route) = > {
this.routeMap[route.path] = route.component;
}); } } Copy the code
router-link
Next, implement initComponents, which registers the router-link and router-view components.
The initComponents method takes a Vue constructor as a parameter to reduce the method and external dependencies.
The router-link component receives a string parameter called TO, which is a link. The router-link itself is converted to label A, and the contents of the router-link are rendered to label A.
export default class VueRouter {
// other code
initComponents(Vue) {
Vue.component("router-link", {
props: {
to: String. }, template: `<a :href="to"><slot></slot></a>`. }); } } Copy the code
Create an init function that wraps initRouteMap and initComponents together for ease of use.
The init method is then called when the Vue instance is created to create the router-link component.
export default class VueRouter {
// other code
static install(Vue) {
if (VueRouter.install.installed) {
return;
} VueRouter.install.installed = true; _Vue = Vue; _Vue.mixin({ beforeCreate() { if (this.$options.router) { _Vue.prototype.$router = this.$options.router; // Call init here this.$options.router.init(); } }, }); } init() { initRouteMap(); initComponents(); } } Copy the code
It’s ready to be tested now.
SRC /router/index.js vue-router replace our own vue router.
// import VueRouter from 'vue-router'
import VueRouter from ".. /.. /vue-router/index";
Copy the code
Start the project.
npm run serve
Copy the code
When you open your browser, the page is blank, but the console gets two errors.
The first mistake is:
vue.runtime.esm.js?2b0e:619 [Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.
Copy the code
This error means that the runtime version of Vue is being used and the template compiler is not available. Templates can be compiled to render functions using precompilation, or using the compiled version of Vue.
Build version of Vue:
-
Runtime version: Does not support template templates and is compiled before packaging.
-
Full version: includes the runtime and compiler, about 10K larger than the runtime version, and converts the template to render as the program runs.
The second error is that the router-view component is not defined because the router-view is not processed yet and can be ignored.
Use the full Vue in VueCLI
Vue Projects created by the Cli use the runtime version of Vue by default because it is more efficient.
If you want to modify the configuration of the Vue Cli project, you need to create the vue.config.js file in the project root directory, which exports a module using the CommonJS specification.
To use the full Vue, set runtimeCompiler to true, which is false by default.
module.exports = {
runtimeCompiler: true.};
Copy the code
Then the project was restarted, and the first problem was solved.
However, the Vue volume of the full version will be 10K larger, and it is run time compilation, which consumes performance, so it is not recommended to use.
The runtime version Vue Render method
The runtime version of Vue does not include a compiler, so the Template option is not supported. The compiler converts the template option to the render function.
We don’t write the render function when we don’t have runtimeCompiler enabled when we write the.vue file. This is because the Webpack configured in the Vue Cli will convert the template in the Vue file to the render function during the code compilation and packaging phase, which is precompiled. The js file we write is not precompiled. So to use the render function in the runtime version of Vue.
First remove vue.config.js.
Modify the initComponents function.
export default class VueRouter {
// other code
initComponents(Vue) {
Vue.component("router-link", {
props: {
to: String. }, // template: `<a :href="to"><slot></slot></a>` render(h) { return h( "a". { attrs: { href: this.to, }, }, [this.$slots.default] ); }, }); } } Copy the code
Render receives an h function that creates a virtual DOM, and render returns the virtual DOM.
The use of the h function has a lot of kinds, specific refer to the official documentation: https://cn.vuejs.org/v2/guide/render-function.html
Restart the project as expected.
router-view
The router-view component is similar to the slot component in providing a placeholder function. According to the routing address, different routing components are obtained and rendered to the location of router-view.
export default class VueRouter {
// other code
initComponents(Vue) {
// other code
const self = this;
Vue.component("router-view", { render(h) { const component = self.routeMap[self.data.current]; return h(component); }, }); } } Copy the code
This completes the router-View component.
But now I try to click on the hyperlink and it doesn’t work. The reason is that the a label will request the server by default, causing the page to refresh.
Therefore, we need to prevent the default behavior of the A tag to request the server, and use the histor. PushState method to change the URL of the navigation bar, and save the changed URL to this.data.current. Because this.data is reactive data.
Example Modify the router-link logic.
export default class VueRouter {
// other code
initComponents(Vue) {
// other code
Vue.component("router-link", {
props: { to: String. }, render(h) { return h( "a". { attrs: { href: this.to, }, on: { click: this.clickHandler, }, }, [this.$slots.default] ); }, methods: { clickHandler(e) { history.pushState({}, "".this.to); this.$router.data.current = this.to; e.preventDefault(); }, }, }); } } Copy the code
Go back to the project and run the project. Click the A TAB to refresh the page normally.
initEvent
While all the functionality has been implemented above, there is one small problem.
Clicking the forward and back buttons in the upper left corner of the browser changes the URL in the address bar, but the page does not change.
The solution to this problem is simple.
The idea is to listen to the popState method and set the value of this.data.current to the URL of the current navigation bar. Since this.data is reactive data, all components that use this.data will be rerendered when this.data changes.
export default class VueRouter {
// other code
init() {
// other code
this.initEvent();
} initEvent(Vue) { window.addEventListener("popstate", () = > { this.data.current = window.location.pathname; }); } } Copy the code
This solves the small problem of the navigation bar going back and forth without refreshing components.
The source code
So far, the simple implementation of vue Router in history mode has been completed.
Attach full source code:
let _Vue = null;
export default class VueRouter {
static install(Vue) {
// 1. Check whether the current plug-in is installed
if (VueRouter.install.installed) { return; } VueRouter.install.installed = true; // 2. Record the Vue constructor as a global variable _Vue = Vue; // 3. Inject the router object passed during the creation of the Vue instance into the Vue instance / / with _Vue.mixin({ beforeCreate() { if (this.$options.router) { _Vue.prototype.$router = this.$options.router; this.$options.router.init(); } }, }); } constructor(options) { this.options = options; this.routeMap = {}; this.data = _Vue.observable({ current: "/". }); } init() { this.initRouterMap(); this.initComponents(_Vue); this.initEvent(); } initRouterMap() { // Iterates over all routing rules and resolves the routing rules into key-value pairs and stores them in the routerMap this.options.routes.forEach((route) = > { this.routeMap[route.path] = route.component; }); } initComponents(Vue) { Vue.component("router-link", { props: { to: String. }, // template: ` // <a :href="to"> // <slot></slot> // </a> / / `, render(h) { return h( "a". { attrs: { href: this.to, }, on: { click: this.clickHandler, }, }, [this.$slots.default] ); }, methods: { clickHandler(e) { history.pushState({}, "".this.to); this.$router.data.current = this.to; e.preventDefault(); }, }, }); const self = this; Vue.component("router-view", { render(h) { console.log(self); const component = self.routeMap[self.data.current]; return h(component); }, }); } initEvent() { window.addEventListener("popstate", () = > { this.data.current = window.location.pathname; }); } } Copy the code
The last word
The past is gone, the future is bright.
This is not a treatise. Except for the title, the content is dry stuff.
But, I want to write a few soft words next.
At a time when many people are afraid or even fearful of job interviews and are frantically brushing their faces away.
You should understand that people who know how to use, implement, or read the source code don’t get the interview. So can people who can create, think and innovate.
You can meet like-minded people and discuss the meaning of frameworks and tools.
You can also discover the pain points in your business and your interests, realize your ideas, and continue to be satisfied by your interests, and continue to progress.
You can also read books to find out more about software.
In short, there are many ways, the way is very wide, it depends on their own practice.
Don’t give up on yourself because you’ve been stupid. Small factories are not the only ones that can make you happy and realize your value.
You need to know what you can do, not what you want.
Do you have the ability to improve yourself? Can you handle 996? Choose the lifestyle that suits you best.
Don’t hurt yourself for money.
But without money, life will hurt you.
This article was typeset using MDNICE