目录
console.log
Protip: Visual Studio Code snippets
JavaScript Arrays
Rendering collections
Key-attribute
Map
Anti-pattern: array indexes as keys
Refactoring modules
When the application breaks
What's the difference between an experienced JavaScript programmer and a rookie? The experienced one uses console.log 10-100 times more.
一个JavaScript 老鸟和菜鸟有什么区别? 老鸟使用 console.log的次数是菜鸟的数十倍甚至数百倍。
实际上,菜鸟比老鸟更需要 console.log (或任何其他调试方法)。
当某些事情不能正常工作时,不要只是猜测错误,而应记录或使用其他调试方法。
注意:如前所说的,使用 console.log 命令进行调试时,不要用Java的方式,将所有东西用'+'连在一起。即不要这么写:
console.log('props value is' + props)
而应该用逗号把要打印的东西分开:
console.log('props value is', props)
如果把一个对象和一个字符串(用加号)连接起来,然后把它记录到控制台上 ,结果将是:
props value is [Object object]
而将对象用逗号分隔,将不同参数传递给 console.log 时,对象的内容将作为有意义的字符串打印到开发者控制台中。
更多关于React 应用调试的内容here。
【高级技巧: Visual Studio Code 的代码片段】
使用 Visual Studio Code能够很容易创建“代码片段(snippets)” ,即快速生成常用代码块的快捷方式,很像 Netbeans 中的“ sout”。
创建代码片段的说明 here。
有用的、现成的代码片段也可以在 VS 代码插件中找到,例如这里.
最重要的片段是用于 console.log() 命令的片段,例如clog:
{
"console.log": {
"prefix": "clog",
"body": [
"console.log('$1')",
],
"description": "Log output to console"
}
}
使用 console.log() 来debug 代码十分常见, Visual Studio Code 有内置的snippet。可以使用 log 和tab键来自动补全
【JavaScript 数组】
我们会经常使用 JavaScript 数组的函数式编程方法,比如 find, filter, 和 map。 它们和 Java 8中的streams 一样遵循一般原则,可以参考 YouTube 视频系列的前三部分 Functional Programming in JavaScript
【渲染集合】
现在编写“前端”或叫浏览器端的应用逻辑。
从如下代码开始:
import React from 'react'
import ReactDOM from 'react-dom'
const notes = [
{
id: 1,
content: 'HTML is easy',
date: '2019-05-30T17:30:31.098Z',
important: true
},
{
id: 2,
content: 'Browser can execute only JavaScript',
date: '2019-05-30T18:39:34.091Z',
important: false
},
{
id: 3,
content: 'GET and POST are the most important methods of HTTP protocol',
date: '2019-05-30T19:20:14.298Z',
important: true
}
]
const App = (props) => {
const { notes } = props
return (
Notes
- {notes[0].content}
- {notes[1].content}
- {notes[2].content}
)
}
ReactDOM.render(
,
document.getElementById('root')
)
每个便笺都包含其文本内容、时间戳以及一个布尔值,用于标记该便笺是否重要,便笺还包含一个惟一的id。
可以通过引用一个硬编码的索引号来访问数组中的对象来渲染单个便笺:
{notes[1].content}
可以使用 map 函数从数组对象生成 React-元素,使解决方案变得更通用。
notes.map(note => {note.content} )
其结果是一个 li 元素的数组。
[
HTML is easy ,
Browser can execute only JavaScript ,
GET and POST are the most important methods of HTTP protocol ,
]
然后可以把这些li元素放在ul 标签中:
const App = (props) => {
const { notes } = props
return (
Notes
{notes.map(note => - {note.content}
)}
)
}
由于生成li 标签的代码是 JavaScript,所以就要像所有其他 JavaScript 代码一样,在 JSX 模板中使用花括号来包装。
利用多行分隔箭头函数的定义提高代码的可读性:
const App = (props) => {
const { notes } = props
return (
Notes
{notes.map(note =>
-
{note.content}
)}
)
}
【Key-属性】
尽管该应用似乎运行良好,但在控制台上有一个警告:
错误消息中的链接 page 说明,列表项,即 map 方法生成的每个元素,都必须有一个唯一的键值: 一个名为key 的属性。
添加上key:
const App = (props) => {
const { notes } = props
return (
Notes
{notes.map(note =>
-
{note.content}
)}
)
}
错误就消失了。
React 使用数组中对象的key属性来确定组件在重新渲染时,更新组件生成的视图。 更多的说明在这里。
理解数组中map方法的工作原理。
应用包含一个称为 notes 的数组
const notes = [
{
id: 1,
content: 'HTML is easy',
date: '2019-05-30T17:30:31.098Z',
important: true
},
{
id: 2,
content: 'Browser can execute only JavaScript',
date: '2019-05-30T18:39:34.091Z',
important: false
},
{
id: 3,
content: 'GET and POST are the most important methods of HTTP protocol',
date: '2019-05-30T19:20:14.298Z',
important: true
}
]
看看 map 是如何工作的。
如果下面的代码被添加到文件的结尾:
const result = notes.map(note => note.id)
console.log(result)
控制台会打印出[1, 2, 3]。
map 总是会创建一个新数组,其元素是从原始数组的元素通过mapping映射创建的,映射的逻辑是使用作为 map 方法传递进去的函数。
这个函数是
note => note.id
这是一个以紧凑形式编写的箭头函数。完整形式如下:
(note) => {
return note.id
}
该函数获取一个 note 对象作为参数,然后返回 其id 字段的值。
如果将命令改为:
const result = notes.map(note => note.content)
结果是一个包含便笺内容的数组。
这已经非常接近使用的React代码:
notes.map(note =>
{note.content}
)
它生成一个li 标签,其中包含每个便笺对象的便笺内容。
由于函数参数的 map 方法
note => {note.content}
【反模式: 将数组的索引作为键】
使用数组的索引作为键,可以使控制台上的错误消息消失。可以通过向 map 方法的回调函数传递的第二个参数来获取索引:
notes.map((note, i) => ...)
当这样调用时,i 根据便笺所在数组中的位置,分配到了索引值。
因此,用于定义行生成而不产生错误的一种方法是:
{notes.map((note, i) =>
-
{note.content}
)}
然而,这可能导致意想不到的问题,即使它似乎能正常工作,参考这里。
【重构模块】
把代码整理一下,因为只对props的字段 notes 属性感兴趣,直接使用解构:
const App = ({ notes }) => { return (
Notes
{notes.map(note =>
-
{note.content}
)}
)
}
将单独显示一个便笺到Note组件:
const Note = ({ note }) => {
return (
{note.content}
)
}
const App = ({ notes }) => {
return (
Notes
{notes.map(note =>
)}
)
}
注意,现在必须为Note 组件定义key 属性,而不是像前面那样为li 标签定义key 属性。
可以在单个文件中编写整个 React 应用。 虽然实践中很少这么用。 通常的做法是将每个组件在其自己的文件中,声明为一个ES6-模块。
我们一直在使用模块。比如文件的前几行:
import React from 'react'
import ReactDOM from 'react-dom'
为了让它们能够在代码中使用,就import 了两个模块: React 模块被放入一个名为 React 的变量中, React-DOM 模块放到了 ReactDOM 变量中。
将我们的Note 组件移动到它自己的模块中。
在较小型的应用中,组件通常放在一个名为components 的目录中,而这个components目录又放在src 目录中。 约定是:按照组件的名称来命名文件。
现在为应用创建一个名为components 的目录,并在其中放置一个名为Note.js 的文件。
Note.js 文件的内容如下:
import React from 'react'
const Note = ({ note }) => {
return (
{note.content}
)
}
export default Note
由于这是一个 React-组件,因此我们必须导入 React。
模块的最后一行 exports ,是在声明模块,即变量Note。
现在使用这个组件的文件,即index.js,可以 import 这个模块了:
import React from 'react'
import ReactDOM from 'react-dom'
import Note from './components/Note'
const App = ({ notes }) => {
// ...
}
模块导出的组件现在可以在变量Note 中使用了,就像之前一样。
当导入自己的组件时,位置必须给出导入文件相对路径:
'./components/Note'
开头的 句点 指的是当前工作目录,因此模块的位置是当前components 的子目录中的一个名为Note.js 的文件。 文件扩展名(.js)可以省略。
App也是一个组件,所以在它自己的模块中声明它。 因为它是应用的根组件,所以将它放在 src 目录中。 文件内容如下:
import React from 'react'
import Note from './components/Note'
const App = ({ notes }) => {
return (
Notes
{notes.map((note) =>
)}
)
}
export default App
index.js 文件剩下的内容是:
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
const notes = [
// ...
]
ReactDOM.render(
,
document.getElementById('root')
)
【当应用挂掉了】
应用挂掉是经常发生的情况。 动态类型语言更是如此,例如 JavaScript,其编译器不检查数据类型,例如函数变量或返回值。
例如,“React 崩掉” 可以是这种姿势:
在这些情况下,最好的方案就是 console.log.。
引起崩溃的代码长这样的:
const Course = ({ course }) => (
)
const App = () => {
const course = {
// ...
}
return (
)
}
通过在代码中添加console.log 命令,深入研究出现故障的原因。 因为要渲染的第一个东西是App 组件,所以值得将第一个console.log 放在那里:
const App = () => {
const course = {
// ...
}
console.log('App works...')
return (
// ..
)
}
要在控制台上看到打印结果,必须翻过长长的红色报错墙。
当打印被发现是有效时,就是时候往更深入的地方打印记录了。 如果组件声明是单个语句,或者声明为了函数而没有返回,则会增加打印到控制台的难度。
const Course = ({ course }) => (
)
这个组件应该更改为更长的形式,以便添加打印:
const Course = ({ course }) => {
console.log(course)
return (
)
}
通常,问题的根源在于,props的类型不同,或者使用了与实际名称不同的名称调用,导致结果解构失败。 通常是去掉解构的方式,来看看 props 中到底包含什么。
const Course = (props) => {
console.log(props)
const { course } = props
return (
)
}