134 Matching Annotations
  1. Sep 2024
    1. Each language server can be installed via a package manager - but often in a different way. For example, the language servers for HTML/CSS/JS are provided by Microsoft, and are installed via npm. Whereas the language server for say, Rust, might be managed by rustup (or Rust itself). Keep this in mind, as you will need to have the relevant package manager installed based on what language servers you need. Microsoft keeps a list of LSP servers where you can check the repositories to see which package manager (if any) you will need to install it.

      不同的 language server 需要通过不同的 package manager 安装。

    2. Mason is an easy way to install these language servers for the programming languages of your choice if you don’t want to install and maintain them yourself.

      Mason 是安装 LSP language servers 的方便工具。

    3. It’s worth noting that these features don’t technically ship directly with Neovim when the language server is configured. It enables the use of them, and you have to get plugins for each feature you want (unix philosophy, baby).

      想要获得这些 servers 提供的特性,需要安装插件。

    4. These servers make it possible to get features such as go-to-definition, auto-complete and syntax errors (like if you spell something wrong or miss a semi-colon - so you don’t rip your hair out trying to find it).

      这些 servers 提供了诸如自动补全、语法错误等特性。

    5. Neovim ships with a built-in LSP client,

      neovim 自带一个 LSP client

    6. When picking a Neovim colorscheme make sure that it’s tree-sitter supported/compatible - Awesome Neovim provides a list of themes which you can use.

      soga,原来 colorscheme 要和 treesitter 兼容

    1. For example, creating a file called lazy.lua and then requiring it in your init.lua with require("lazy") is going to cause some problems... Remember the snippet above does the exact same thing to find lazy on the runtimepath, but now there are two modules named lazy - one in your config, and one in the Neovim data directory. This will essentially cause your entire configuration to break.

      在 /lua 文件夹里使用 lazy 模块要注意冲突

    1. I hope that you at least found this post useful though in understanding the runtimepath and which files are automatically ran on startup, why people structure their lua/ folders in different ways, and how you can begin structuring your configuration.

      虽然 /plugin 和 /lua 文件夹都是 runtimepath,但是 plugin 里的 lua 文件就不需要 require 就能用运行,lua 文件夹里的文件都需要在外面的 init.lua 中 require

    2. The reason you tend to see most users throwing their entire config in the lua/ directory is because you can use Lua modules to organize your config in a nice, neat way.

      推荐使用 /lua 文件夹来进行 neovim 配置是因为可以使用 Lua modules 来组织配置。

    1. Control characters other than tab (U+0000 to U+0008, U+000A to U+001F, U+007F) are not permitted in comments.

      疑问

      不明白什么是 control characters

    2. Newline means LF (0x0A) or CRLF (0x0D 0x0A).
    3. TOML is designed to map unambiguously to a hash table.

      问题

      以前这是个问题吗?

    1. There are also other use cases for refs than accessing React components.

      要点

      React

      其他一些 useRef 的使用场景

    2. The function that creates the component is wrapped inside of a forwardRef function call. This way the component can access the ref that is assigned to it. The component uses the useImperativeHandle hook to make its toggleVisibility function available outside of the component. We can now hide the form by calling noteFormRef.current.toggleVisibility() after a new note has been created:

      要点

      React

      在被引用的组件里,组件定义被 forwardRef 包裹,同时里面使用 useImperativeHandle 来定义能被外面获取到的内容。

    3. The useRef hook is used to create a noteFormRef reference, that is assigned to the Togglable component containing the creation note form. The noteFormRef variable acts as a reference to the component. This hook ensures the same reference (ref) that is kept throughout re-renders of the component.

      要点

      React

      useRef hooks 创建了一个引用,可以用来引用 component

    4. React documentation says the following about where to place the state: Sometimes, you want the state of two components to always change together. To do it, remove state from both of them, move it to their closest common parent, and then pass it down to them via props. This is known as lifting state up, and it’s one of the most common things you will do writing React code. If we think about the state of the forms, so for example the contents of a new note before it has been created, the App component does not need it for anything. We could just as well move the state of the forms to the corresponding components.

      要点

      关于 React 中组件定义的位置。注意 lifting stat up 和把状态放到子组件中两种情况。

    5. The new and interesting part of the code is props.children, which is used for referencing the child components of the component. The child components are the React elements that we define between the opening and closing tags of a component.

      要点

      在 React 中,定义在一个 component 开闭标签之间的内容就是这个组件的 child components

    6. If loginVisible is false, then display will not receive any value related to the visibility of the component.

      要点

      如果 display: "" 的话,就是显示

    1. No matter how the validity of tokens is checked and ensured, saving a token in the local storage might contain a security risk if the application has a security vulnerability that allows Cross Site Scripting (XSS) attacks. An XSS attack is possible if the application would allow a user to inject arbitrary JavaScript code (e.g. using a form) that the app would then execute. When using React sensibly it should not be possible since React sanitizes all text that it renders, meaning that it is not executing the rendered content as JavaScript. If one wants to play safe, the best option is to not store a token in local storage. This might be an option in situations where leaking a token might have tragic consequences. It has been suggested that the identity of a signed-in user should be saved as httpOnly cookies, so that JavaScript code could not have any access to the token. The drawback of this solution is that it would make implementing SPA applications a bit more complex. One would need at least to implement a separate page for logging in. However, it is good to notice that even the use of httpOnly cookies does not guarantee anything. It has even been suggested that httpOnly cookies are not any safer than the use of local storage. So no matter the used solution the most important thing is to minimize the risk of XSS attacks altogether.

      延伸

      Cross Site Scripting 注入攻击 httpOnly cookies -> 让 SPA 不好实现

    2. The empty array as the parameter of the effect ensures that the effect is executed only when the component is rendered for the first time.

      要点

      useEffect 中如果设定 empty array 的话,就代表只有第一次渲染的时候执行 effect

    3. Values saved to the storage are DOMstrings, so we cannot save a JavaScript object as it is. The object has to be parsed to JSON first, with the method JSON.stringify. Correspondingly, when a JSON object is read from the local storage, it has to be parsed back to JavaScript with JSON.parse.

      要点

      local storage 里存储的是 DOMString,因此 JS 对象的存取,需要 stringify 和 parse

    4. Values in the local storage are persisted even when the page is re-rendered. The storage is origin-specific so each web application has its own storage.

      要点

      local storage 是 origin-specific 的,因此每一个 web app 都有一个 storage。

    5. This problem is easily solved by saving the login details to local storage. Local Storage is a key-value database in the browser.

      要点

      local storage 是浏览器中的 key-value 数据库

      API: window.localStorage.setItem / getItem / removeItem

    6. The noteService module contains a private variable called token. Its value can be changed with the setToken function, which is exported by the module. create, now with async/await syntax, sets the token to the Authorization header. The header is given to axios as the third parameter of the post method.

      要点

      axios 中设置 header 是通过第三个参数,传递一个对象

    7. A slightly odd looking, but commonly used React trick is used to render the forms conditionally:

      延伸

      React 中的条件渲染技巧

    1. This is most likely useful when doing the fix.

      要点

      在 .post() 方法后面使用 .set('Authorization', 'Bear xxx') 来设置测试中的请求头

    2. the field blog.user does not contain a string, but an object. So if you want to compare the ID of the object fetched from the database and a string ID, a normal comparison operation does not work. The ID fetched from the database must be parsed into a string first.

      重点

      mongoose 关联的 ref 类型是 ObjectId,因此要比较 ID 内容,使用 toString 方法。

    3. Both the new blog creation and blog deletion need to find out the identity of the user who is doing the operation. The middleware tokenExtractor that we did in exercise 4.20 helps but still both the handlers of post and delete operations need to find out who the user holding a specific token is.

      重点

      这里就是后端的常见操作,当寻找用户的操作要在 API 里完成的时候,就可以抽出来放到中间件里去做。

    4. As can be seen, this happens by chaining multiple middlewares as the arguments of the function use. It would also be possible to register a middleware only for a specific operation:

      重点

      middleware 可以针对某一个 endpoint 使用,以及某一个 route 使用。

    5. NB if you decide to define tests on multiple files, you should note that by default each test file is executed in its own process (see Test execution model in the documentation). The consequence of this is that different test files are executed at the same time. Since the tests share the same database, simultaneous execution may cause problems, which can be avoided by executing the tests with the option --test-concurrency=1, i.e. defining them to be executed sequentially.

      重点

      咋不早说... 每个测试文件都是在自己的进程中运行的,因此会并行跑,使用 --test-concurrency=1 来串行跑

    6. Usernames, passwords and applications using token authentication must always be used over HTTPS. We could use a Node HTTPS server in our application instead of the HTTP server (it requires more configuration). On the other hand, the production version of our application is in Fly.io, so our application stays secure: Fly.io routes all traffic between a browser and the Fly.io server over HTTPS.

      拓展

      Node 也可以配置使用 HTTPS 服务器来运行。

    7. Limiting creating new notes to logged-in users

      拓展

      • 用户 token 过期之后,重新登陆
      • 使用数据库在后端存 token -> 使用 redis 存 token
        • 后端 token 没有个人信息
      • 使用 cookie 传递 token
    8. It is also quite usual that instead of using Authorization-header, cookies are used as the mechanism for transferring the token between the client and the server.

      重点

      关于 token-based authentication 的问题

      还有一种常见的操作是,不实用 Authorization header,而是使用 cookie 来作为在 server 和 client 之间传递 token 的机制。

    9. When server-side sessions are used, the token is quite often just a random string, that does not include any information about the user as it is quite often the case when jwt-tokens are used. For each API request, the server fetches the relevant information about the identity of the user from the database.

      重点

      关于 token-based authentication 的问题

      使用 server-side sessions 的话,token 就只是 random 字符串,不像 jwt 一样包含用户的个人信息。

    10. The negative aspect of server-side sessions is the increased complexity in the backend and also the effect on performance since the token validity needs to be checked for each API request to the database. Database access is considerably slower compared to checking the validity of the token itself. That is why it is quite common to save the session corresponding to a token to a key-value database such as Redis, that is limited in functionality compared to eg. MongoDB or a relational database, but extremely fast in some usage scenarios.

      重点

      关于 token-based authentication 的问题

      server-side session 的问题是增加了后端的复杂性,而且每个 API 请求都需要走数据库进行校验,这很慢。因此通常会把 token 存到键值对数据库 redis 中。

    11. The other solution is to save info about each token to the backend database and to check for each API request if the access rights corresponding to the tokens are still valid. With this scheme, access rights can be revoked at any time. This kind of solution is often called a server-side session.

      重点

      关于 token-based authentication 的问题

      另一种解决方案是把 token 的信息存到后端,然后每个 API 请求都检查 token 的权限是否有效,这就是 server-side session。

    12. Once the token expires, the client app needs to get a new token. Usually, this happens by forcing the user to re-login to the app. The error handling middleware should be extended to give a proper error in the case of an expired token

      重点

      关于 token-based authentication 的问题

      第一种方法是给一个过期时间,时间到了,通常来说要迫使用户重新登陆(这个是需要实现的还是浏览器自动就有的?猜测是前者)

      但这样的做法实际上是用户体验不好的。

    13. Token authentication is pretty easy to implement, but it contains one problem. Once the API user, eg. a React app gets a token, the API has a blind trust to the token holder. What if the access rights of the token holder should be revoked?

      重点

      关于 token-based authentication 的

      最简单的实现问题在于 API 对于有 token 的人完全信任,如果遇到需要撤销 token 的情况怎么办?

    14. If the application has multiple interfaces requiring identification, JWT's validation should be separated into its own middleware. An existing library like express-jwt could also be used.

      #拓展 如果有多个接口(几乎是一定的)需要验证身份,那么 JWT 验证应该被放到自己的中间件中。

      有一个库提供这个功能 express-jwt

    15. The helper function getTokenFrom isolates the token from the authorization header. The validity of the token is checked with jwt.verify. The method also decodes the token, or returns the Object which the token was based on.

      重点

      原来从 header 中取得 token 就是操作字符串。 jwt.verify() 方法来校验 token

    16. There are several ways of sending the token from the browser to the server. We will use the Authorization header. The header also tells which authentication scheme is used. This can be necessary if the server offers multiple ways to authenticate. Identifying the scheme tells the server how the attached credentials should be interpreted.

      重点

      我们在进行 authentication 的时候,浏览器通过 Authorization header 来给服务器发送 token。这个 header 不仅包含了 token,还有使用了何种 Scheme。

    17. If the password is correct, a token is created with the method jwt.sign. The token contains the username and the user id in a digitally signed form.

      重点

      如果用户密码正确,就返回一个 jwt token,里面包含了 username user_id,数字签名的形式。

      签名的过程中使用了一个 SECRET.

    18. ecause the passwords themselves are not saved to the database, but hashes calculated from the passwords, the bcrypt.compare method is used to check if the password is correct:

      重点

      bcrypt.compare() 方法用来检查密码是否正确

    19. Let's first implement the functionality for logging in. Install the jsonwebtoken library, which allows us to generate JSON web tokens.

      重点

      我们使用 jsonwebtoken 这个库来生成 jwt token

    20. The principles of token-based authentication are depicted in the following sequence diagram:

      重点

      整个 authentication 的过程如下图 登陆请求的返回有一个 token,浏览器保存下来这个 token

    21. We will now implement support for token-based authentication to the backend.

      拓展阅读

      这里实现的是 token-based authentication

    1. NOTE: At this stage, firstly, some tests will fail. We will leave fixing the tests to a non-compulsory exercise.

      拓展

      这里花了不少时间,但是两个测试文件会相互影响,如何让 Node 不并行运行测试文件,或者更好地准备测试数据,是后续可以做的。

      还有一个问题是,test_helper 里面的函数没有像实现一样,应用 populate 方法改变返回,这里也是测试脆弱的一点。

    2. We could also implement other validations into the user creation. We could check that the username is long enough, that the username only consists of permitted characters, or that the password is strong enough. Implementing these functionalities is left as an optional exercise.

      拓展练习

      实现用户创建的更多校验:用户名长度,包含字符,密码强度

    3. However, we want to be careful when using the uniqueness index. If there are already documents in the database that violate the uniqueness condition, no index will be created. So when adding a uniqueness index, make sure that the database is in a healthy state! The test above added the user with username root to the database twice, and these must be removed for the index to be formed and the code to work.

      延伸阅读

      使用 Schema 上字段的 unique 选项来保证一个字短的唯一性的时候,需要确保数据库状态是好的,如果现有数据违反了唯一性,索引不会被建立起来。

    4. The fundamentals of storing passwords are outside the scope of this course material. We will not discuss what the magic number 10 assigned to the saltRounds variable means, but you can read more about it in the linked material.

      延伸阅读

      这里介绍了关于存储密码以及 salt 的知识。

    5. It's important to understand that the database does not know that the ids stored in the user field of the notes collection reference documents in the user collection. The functionality of the populate method of Mongoose is based on the fact that we have defined "types" to the references in the Mongoose schema with the ref option:

      再一次,数据库并不知道 collection 之间是关联的,是因为我们在 schema 定义中指定了它们之间的关联。

    6. We can use the populate method for choosing the fields we want to include from the documents. In addition to the field id we are now only interested in content and important. The selection of fields is done with the Mongo syntax:

      关联查询的时候怎么指定返回的内容,参考 Mongo 语法

    7. The argument given to the populate method defines that the ids referencing note objects in the notes field of the user document will be replaced by the referenced note documents.

      这句话着重理解:也就是传给 populate 方法的参数的含义是,我在查询 user 时候,user notes 字段里面关联的 id,都会被替代为真实的 notes document。

    8. Mongoose library can do some of these joins for us. Mongoose accomplishes the join by doing multiple queries, which is different from join queries in relational databases which are transactional, meaning that the state of the database does not change during the time that the query is made. With join queries in Mongoose, nothing can guarantee that the state between the collections being joined is consistent, meaning that if we make a query that joins the user and notes collections, the state of the collections may change during the query.

      Mongoose 提供的 join 是利用多次查询实现的。一个问题是不保证 transactional,进行查询的时候,数据库的状态可能会变。

    9. Mongoose validations do not provide a direct way to check the uniqueness of a field value. However, it is possible to achieve uniqueness by defining uniqueness index for a field. The definition is done as follows:

      Mongoose 中如何使某一个 field unique check

    10. In stark contrast to the conventions of relational databases, references are now stored in both documents: the note references the user who created it, and the user has an array of references to all of the notes created by them.

      这里表明,在传统的关系型数据库中,reference 只存储在一边。

    11. The type of the field is ObjectId that references note-style documents. Mongo does not inherently know that this is a field that references notes, the syntax is purely related to and defined by Mongoose.

      定义不同 schema 之间的 ref,Mongo 并不知道,是由 Mongoose 管理的。

    12. The structure and schema of the database are not as self-evident as it was with relational databases. The chosen schema must support the use cases of the application the best. This is not a simple design decision to make, as all use cases of the applications are not known when the design decision is made. Paradoxically, schema-less databases like Mongo require developers to make far more radical design decisions about data organization at the beginning of the project than relational databases with schemas. On average, relational databases offer a more or less suitable way of organizing data for many applications.

      文档数据库的结构相比起关系型数据库会更灵活,因此其实需要开发者在开始的时候就做出更重要的设计决定。这些决定不简单是因为 use case 在设计的时候不是明确的。

    1. The then-chain is alright, but we can do better. The generator functions introduced in ES6 provided a clever way of writing asynchronous code in a way that "looks synchronous". The syntax is a bit clunky and not widely used.

      延伸阅读

      接下来 ES6 中的 generator function 再提升了一点(不了解)

    2. The documentation for supertest says the following: if the server is not already listening for connections then it is bound to an ephemeral port for you so there is no need to keep track of ports. In other words, supertest takes care that the application being tested is started at the port that it uses internally.

      supertest 控制了应用启动在指定的 port,因此我们只需要引入 app.js,不用自己在指定端口启动。

    3. The Promise.all method can be used for transforming an array of promises into a single promise, that will be fulfilled once every promise in the array passed to it as an argument is resolved. The last line of code await Promise.all(promiseArray) waits until every promise for saving a note is finished, meaning that the database has been initialized.

      Promise.all() 的原理。

    4. Promise.all executes the promises it receives in parallel. If the promises need to be executed in a particular order, this will be problematic. In situations like this, the operations can be executed inside of a for...of block, that guarantees a specific execution order.

      但是 Promise.all() 方法不保证执行顺序,如果需要顺序的话,就得在循环里,一次一次等待了。

    5. The problem is that every iteration of the forEach loop generates an asynchronous operation, and beforeEach won't wait for them to finish executing. In other words, the await commands defined inside of the forEach loop are not in the beforeEach function, but in separate functions that beforeEach will not wait for.

      如果 async 方法里有 async 操作,不做任何处理的情况下,外层的 await 是不会等到内层的异步操作结束的。

    6. Because of the library, we do not need the next(exception) call anymore. The library handles everything under the hood. If an exception occurs in an async route, the execution is automatically passed to the error-handling middleware.

      这个库自动 handle 了捕获异常,以及 pass to next 的情况。

    7. One starts to wonder if it would be possible to refactor the code to eliminate the catch from the methods? The express-async-errors library has a solution for this.

      express-async-errors 库,可以让我们移除针对 async 代码的 try catch 写法。

    8. The reason for this is that strictEqual uses the method Object.is to compare similarity, i.e. it compares whether the objects are the same. In our case, it is enough to check that the contents of the objects, i.e. the values of their fields, are the same. For this purpose deepStrictEqual is suitable.

      Node 中的 equals 比较,strictEqual 比较引用,deepStrictEqual 比较对象内容。

    9. When code gets refactored, there is always the risk of regression, meaning that existing functionality may break.

      当代码重构的时候,有回归的风险

    10. The await keyword can't be used just anywhere in JavaScript code. Using await is possible only inside of an async function.

      await 关键字只能在 async 函数里使用

    11. The async and await keywords introduced in ES7 bring the same functionality as the generators, but in an understandable and syntactically cleaner way to the hands of all citizens of the JavaScript world.

      ES7 中的 async await 更好理解。

    12. By chaining promises we could keep the situation somewhat under control, and avoid callback hell by creating a fairly clean chain of then method calls. We have seen a few of these during the course. To illustrate this, you can view an artificial example of a function that fetches all notes and then deletes the first one:

      其次 promise chaining 提升了一点可读性

    13. All of the code we want to execute once the operation finishes is written in the callback function. If we wanted to make several asynchronous function calls in sequence, the situation would soon become painful. The asynchronous calls would have to be made in the callback. This would likely lead to complicated code and could potentially give birth to a so-called callback hell.

      对于异步代码的书写,首先是 callback hell

    14. The --tests-by-name-pattern option can be used for running tests with a specific name:

      使用 ---test-name-pattern 可以根据测试的 describe 信息来筛选需要运行的测试

    15. There are a few different ways of accomplishing this, one of which is the only method. With this method we can define in the code what tests should be executed:

      使用 test.only 方法,配合 npm test -- --test-only 命令,可以只运行指定测试。

  2. Aug 2024
    1. Both tests store the response of the request to the response variable, and unlike the previous test that used the methods provided by supertest for verifying the status code and headers, this time we are inspecting the response data stored in response.body property. Our tests verify the format and content of the response data with the method strictEqual of the assert-library.

      之前使用 supertest 库的方法来验证 HTTP 的状态码和头信息,这里是用 node 的 assert 库方法来验证内容。

    2. The problem here, however, is that when using a string, the value of the header must be exactly the same. For the regex we defined, it is acceptable that the header contains the string in question.

      在 superagent 对象的 expect 方法中, api 调用的 expect 方法中使用 string 就是精准匹配,使用正则表达式就是包含匹配。

    3. The config module that we have implemented slightly resembles the node-config package. Writing our implementation is justified since our application is simple, and also because it teaches us valuable lessons.

      node.js 中关于 configuration 的一个库 node-config

    4. The convention in Node is to define the execution mode of the application with the NODE_ENV environment variable. In our current application, we only load the environment variables defined in the .env file if the application is not in production mode.

      Node 中的约定是根据 NODE_ENV 环境变量来确定应用的运行模式。

      当后端运行在 host server 的时候,是 production mode。

      我们的应用在 non production mode 的时候才加载 .env 中的变量(规定是这样?)

    5. Since our application's backend is still relatively simple, we will decide to test the entire application through its REST API, so that the database is also included. This kind of testing where multiple components of the system are being tested as a group is called integration testing.

      一次测试系统的多个组件(比如后端逻辑+数据库)的测试叫做集成测试。

    6. In some situations, it can be beneficial to implement some of the backend tests by mocking the database instead of using a real database. One library that could be used for this is mongodb-memory-server.

      MERN 这边后端也存在 mock 数据库进行测试的库,mongodb-memory-server 是一个。

    1. VS Code has a handy feature that allows you to see where your modules have been exported. This can be very helpful for refactoring. For example, if you decide to split a function into two separate functions, your code could break if you don't modify all the usages. This is difficult if you don't know where they are. However, you need to define your exports in a particular way for this to work. If you right-click on a variable in the location it is exported from and select "Find All References", it will show you everywhere the variable is imported. However, if you assign an object directly to module.exports, it will not work. A workaround is to assign the object you want to export to a named variable and then export the named variable. It also will not work if you destructure where you are importing; you have to import the named variable and then destructure, or just use dot notation to use the functions contained in the named variable. The nature of VS Code bleeding into how you write your code is probably not ideal, so you need to decide for yourself if the trade-off is worthwhile.

      Neovim 里怎么来找一个变量的 reference

    2. There is no strict directory structure or file naming convention that is required for Express applications. In contrast, Ruby on Rails does require a specific structure. Our current structure simply follows some of the best practices that you can come across on the internet.

      express 应用的命名和结构没有严格规定,但是对比来说,Ruby on Rails 的文件需要指定的结构。

    3. The responsibility of establishing the connection to the database has been given to the app.js module. The note.js file under the models directory only defines the Mongoose schema for notes.

      由 app 建立连接,models 文件只定义 schema

    4. The index.js file only imports the actual application from the app.js file and then starts the application. The function info of the logger-module is used for the console printout telling that the application is running.

      index.js 的作用仅仅是导入 application 开始运行

    5. Extracting logging into its own module is a good idea in several ways. If we wanted to start writing logs to a file or send them to an external logging service like graylog or papertrail we would only have to make changes in one place.

      把 logger 抽到一个 util 的好处是,如果更改了 log 的存储位置,我们全局只需要修改一个地方。

    6. Before we move into the topic of testing, we will modify the structure of our project to adhere to Node.js best practices. Once we make the changes to the directory structure of our project, we will end up with the following structure:

      Node 关于 package structure 有最佳实践

    1. 只有在后端的所有内容都经过验证并正常工作后,才是测试前端与后端是否协同工作的好时机。仅通过前端进行测试效率极低。

      单独测试后端!

    2. 通过Note模型的find方法从数据库中检索对象。该方法的参数是一个表示搜索条件的对象。由于参数是一个空对象{},我们得到了notes集合中存储的所有笔记。 搜索条件遵循Mongo搜索查询syntax。

      MongoDB 的查询语法

    1. rm -rf package-lock.json rm -rf node_modules npm cache clean --force npm install

      这部分可能也有用

    1. 让我们在路由之后添加以下中间件,用于捕捉向不存在的路由发出的请求。对于这些请求,中间件将返回一个 JSON 格式的错误信息。

      定义一个处理不存在路由的中间件。

    2. HTTP GET 请求应该是 安全的 。 特别是,约定俗成的是,GET 和 HEAD 方法不应具有除检索以外的行动意义。这些方法应该被认为是 " 安全的 "。。 安全意味着执行的请求不能在服务器中引起任何 副作用 。我们所说的副作用是指数据库的状态不能因为请求而改变,而且响应必须只返回服务器上已经存在的数据。

      HTTP GET 请求

    3. 如果内容属性有一个值,笔记将基于收到的数据。如前所述,在服务器上生成时间戳比在浏览器中生成时间戳更好,因为我们不能相信运行浏览器的主机有正确的时钟设置。现在,date 属性的生成是由服务器完成的。

      一个后端重要的内容

    4. 如果删除资源是成功的,也就是说,笔记存在并且被删除了,我们用状态代码 204 无内容 来响应请求,并且在响应中不返回数据。 对于在资源不存在的情况下应该向 DELETE 请求返回什么状态码,目前还没有达成共识。实际上,唯一的两个选择是 204 和 404。为了简单起见,我们的应用在这两种情况下都会用 204 来响应。

      这里提到了 DELETE 请求的返回响应码和响应体的情况。

    5. 无论如何,我们可以通过 覆盖默认的 NOT FOUND 信息 来提供一个关于发送 404 错误的原因的线索。

      这个帖子告诉了一个方法,通过设置 response 上的 statusMessage 来覆盖默认的信息。

    6. Representational State Transfer,又称 REST,于 2000 年在 Roy Fielding 的 论文 中提出。REST 是一种架构风格,旨在建立可扩展的网络应用。 我们不打算深入研究 Fielding 对 REST 的定义,也不打算花时间去思考什么是 RESTful 和什么不是。相反,我们将采取一个更 狭窄的观点,只关注 RESTful APIs 在网络应用中的典型理解。事实上,REST 的原始定义甚至不限于网络应用。

      REST 的应用范围不限于 Web 开发,这只是其中的一个应用。

    7. 用 Node 内置的 http 网络服务器直接实现我们的服务器代码是可行的。然而,这很麻烦,特别是一旦应用的规模扩大。 许多库已经被开发出来,通过提供一个更讨人喜欢的接口来与内置的 http 模块一起工作,从而缓解 Node 的服务器端开发。这些库的目的是为我们通常需要建立后端服务器的一般使用情况提供一个更好的抽象。到目前为止,用于这一目的的最流行的库是 express。

      后端框架/库的目的是为了搭建后端服务器的一般情况提供一个更好的抽象。

    1. 我们已经使用了与React版本16.8.0一起引入的状态钩子,它为定义为函数的React组件--所谓的功能组件提供状态。16.8.0版本还引入了效果钩子这个新功能。按照官方文档的说法。

      与外部系统连接的时候,使用 useEffect

    1. Building a static version requires a lot of typing and no thinking, but adding interactivity requires a lot of thinking and not a lot of typing.

      先用数据构建出一个静态版本的应用,这部分不需要太多思考,而增加交互性的部分需要更多思考。

    2. This is a matter of preference, and you could go either way. For this example, it is a part of ProductTable because it appears inside the ProductTable’s list. However, if this header grows to be complex (e.g., if you add sorting), you can move it into its own ProductTableHeader component.

      一个 UI 部分是另一个组件的一部分,还是单独成为一个组件,有时候是 a matter of preference,也和这部分 UI 多么复杂有关。

    1. This time, we can't avoid passing the position to the Square. After all, how can the Square know where to move the dragged knight if the Square doesn't know its own position? On the other hand, it still feels wrong because the Square as an entity in our application has not changed, and if it used to be simple, why complicate it? When you face this dilemma, it's time to separate the smart and dumb components.

      这里讲到了 React state 的一些知识。

    2. React is not opinionated about the state management or the data flow; you can use Flux, Redux, Rx or even Backbone nah, avoid fat models and separate your reads from writes.

      wow 这里引用了 Martin Fawler 的文章 CQRS

    3. I'm going to do this every time I work on another component, so that I always have something to render. In a larger app, I would use a component playground like cosmos so I'd never write the components in the dark.

      一个开发技巧:测试一下新写的组件 ok

    1. Meet the drag sources and the drop targets, the primary abstraction units of React DnD. They really tie the types, the items, the side effects, and the collecting functions together with your components.

      重要

      drag sources 和 drag targets 是最关键的抽象概念,连接了之前所有的环节。

    2. In the component's render method, we are then able to access both the data obtained from the monitor, and the function obtained from the connector:

      这里提到了在 component 的 render 函数里怎么使用。

    3. If the backend handles the DOM events, but the components use React to describe the DOM, how does the backend know which DOM nodes to listen to? Enter the connectors. The connectors let you assign one of the predefined roles (a drag source, a drag preview, or a drop target) to the DOM nodes in your render function.

      这里又提到了 connector 的角色,并且它是被传入我们 collecting function 中的。

    4. For each component that needs to track the drag and drop state, you can define a collecting function that retrieves the relevant bits of it from the monitors. React DnD then takes care of timely calling your collecting function and merging its return value into your components' props.

      这里提到了 collectiong function,不明觉厉。

    5. React DnD exposes this state to your components via a few tiny wrappers over the internal state storage called the monitors. The monitors let you update the props of your components in response to the drag and drop state changes.

      这里关于拖拽的状态管理,提到了一个 monitors 的概念。

    6. You're probably going to have an enumeration of the type constants in your application, similar to how you may have an enumeration of the Redux action types.

      React DnD 这个应用借鉴了 Redux 的设计思想。

    1. import了三个模块,使它们可以在该文件中使用。模块React被放入变量React,模块react-dom被放入变量ReactDOM,而定义应用主要组件的模块被放入变量App。

      稍后

      详细学习一下关于 Javascript 的 import

    2. 注意,现在必须为Note组件定义key属性,而不是像以前那样为li标签定义。

      如果使用 map 渲染列表元素的时候,回调函数返回的是我们抽出来的组件(不是原始的 li),那么 key 定义在自定义组件上。

    3. 然而,这是不推荐的,即使它看起来工作得很好,也会产生想不到的问题。 在这篇文章中可以阅读更多关于这个问题的内容。

      不推荐使用 array index as keys 的原因:

      稍后

      https://robinpokorny.medium.com/index-as-a-key-is-an-anti-pattern-e0349aece318

    4. 我们可以通过使用数组索引作为键来使控制台中的错误信息消失。通过向map方法的回调函数传递第二个参数,可以拿到索引。 notes.map((note, i) => ...)copy 当这样调用时,i被分配为笔记所在的数组中的索引值。

      数组的 map 方法,回调函数的第二个参数可以获取到 index。

    5. React使用数组中对象的键属性来决定如何在组件重新渲染时更新该组件生成的视图。关于这一点,更多的可以查看React文档。

      使用 map 生成的元素,每一个都必须有一个 key 属性,原因在于如下文档:

      稍后

      https://legacy.reactjs.org/docs/reconciliation.html#recursing-on-children

    6. 创建片段的说明可以查看这里。 有用的、现成的片段也可以在市场中作为VS Code插件找到。 最重要的片段是用于console.log()命令的片段,例如,clog。这可以这样创建。 { "console.log": { "prefix": "clog", "body": [ "console.log('$1')", ], "description": "Log output to console" } }copy 使用console.log()来调试你的代码非常普遍,因此Visual Studio Code内置了这个片段。要使用它,请输入log并点击tab来自动完成。更多功能的console.log()片段扩展可以在市场中找到。

      稍后

      关于在 VS Code 中配置代码片段快捷键。

    7. 相反,当你把对象作为不同的参数用逗号隔开传递给console.log,就像我们上面的第二个例子,对象的内容被打印到开发者控制台,成为可检查的字符串。

      当打印多个对象到 console.log 中时,它们都会变成可检查的字符串

    8. 当某些东西不工作时,不要只是猜测什么是错的。相反,要记录或使用一些其他的调试方法。

      当某些东西不工作时,不要只是猜测什么是错的。相反,要记录或使用一些其他的调试方法。

    1. 这是全栈开发课程中 React 入门的部分

    2. 在所介绍的两种定义事件处理程序的方式中,选择哪一种主要是口味问题。

      这里强调了,如何定义事件处理程序是风格问题。两种风格:1)把事件处理函数定义成一个调用(返回定制化函数);2)把事件处理函数定义称谓一个函数

    3. 返回的函数可以被利用来定义可以用参数定制的通用功能。创建事件处理程序的hello函数可以被认为是一个工厂,它产生了定制的事件处理程序,旨在向用户问好。

      React 中,事件处理程序不能是一个函数的调用,而是一个函数或者函数引用。但这里介绍了一个“函数工厂”的模式,就是调用返回函数的函数,来定制化函数。

    4. Dev tools按照定义的顺序显示钩子的状态。

      React 开发者工具中展示的 Hooks 顺序是按照 App 中定义的顺序来的。

    5. 记录到控制台决不是调试我们的应用的唯一方法。您可以在Chrome开发者控制台的调试器中暂停应用代码的执行,方法是在代码的任何地方写下debugger命令。 一旦执行到debugger命令被执行的地方,执行将暂停。

      在代码中的任何地方加入 debugger 命令,然后浏览器的 Source 标签页进行断点调试。

    6. NB 当你使用console.log进行调试时,不要用加号运算符以类似Java的方式组合objects。不是写: console.log('props value is ' + props)copy 而是用逗号把你想记录到控制台的东西分开。 console.log('props value is', props)copy 如果你使用类似Java的方式将一个字符串与一个对象连接起来,你最终会得到一个相当不可靠的日志信息。

      对 Java 程序员太良心了,告诉我们 console.log 里用逗号分隔就行。

    7. 在本课程中,我们使用state hook来为我们的React组件添加状态,这是React较新版本的一部分,从16.8.0起就可以使用。在增加钩子之前,没有办法向功能组件添加状态。需要状态的组件必须被定义为class组件,使用JavaScript的类语法。 在这个课程中,我们做了一个略显激进的决定,从第一天开始就完全使用钩子,以确保我们学习React的当前和未来风格。即使功能组件是React的未来,学习类的语法仍然很重要,因为有数十亿行的React遗留代码,你有一天可能会维护它们。这同样适用于你在互联网上偶然发现的React的文档和例子。

      在 16.8.0 之前没有 React Hooks 的时候,需要状态的组件必须被定义为 class。以后在维护遗留代码的时候可能会碰到。

    8. 存储在allClicks中的那块状态现在被设置为一个数组,它包含了之前状态数组的所有项目和字母L。将新的项目添加到数组中是通过concat方法完成的,该方法并不改变现有的数组,而是返回一个数组的新副本,并将项目添加到其中。 如前所述,在JavaScript中也可以用push方法向数组中添加项。如果我们通过把项目推送到allClicks数组中,然后更新状态来添加项目,这个应用看起来仍然可以工作。 const handleLeftClick = () => { allClicks.push('L') setAll(allClicks) setLeft(left + 1) }copy 然而,不要样做。如前所述,像allClicks这样的React组件的状态是不能直接改变的。即使改变状态在某些情况下看起来是有效的,它也会导致很难调试的问题。

      这个例子再一次说明,更新 state 状态的时候,不要更改原来的 state,而是生成一个全新的状态,然后更新进去。

  3. Jun 2023
    1. This article assumes that the server is started using the default configuration and that no server ports are changed.

      用 docker 实现的

  4. Nov 2022
  5. Oct 2022
    1. the @ConditionalOnProperty enables bean registration only if an environment property is present and has a specific value.

      bean registration

    1. However, developing a clear understanding of each of these concepts and their role in organizing code is critical to mastering the art of programming.

      对象、函数、解释器,掌握各个概念之间的关系和它们在代码中的角色,对编码能力非常重要。

    2. The process by which the former leads to the latter may be complex, but we can apply that process using only a simple expression because that complexity is tucked away within a function.

      网络请求的过程可能很复杂,但是 function 存在的意义就是它隐去了细节,只需要一个表达式调用就可以使用它的功能。

    1. “Starters”.

      TODO

    2. embedded servlet containers

      Servlet 的概念...

    3. non-functional features

      这些功能似乎才是 Spring 提供的便利所在...

    4. You can use Spring Boot to create Java applications that can be started by using java -jar or more traditional war deployments. We also provide a command line tool that runs “spring scripts”.

      不应该忽视启动正常 Java 应用程序的方式,以及用 Spring 开发的应用程序在其中有何区别。