今天在开发 React 时发现每次保存 js 文件都会自动改变代码格式导致一大堆报错：经过查询发现时由于安装的 JS-CSS-HTML Formatter 插件默认会在保存文件时对文件格式进行处理。但是堆 React 代码的自动处理有问题导致无法正常进行开发。只需要修改其配置文件将保存文件时自动处理关掉即可。快捷键 cmd + shift + p 打开控制器，输入 formatter：点击 formatter config 修改配置文件，将第一句 "onSave": true 改为 false 保存即可。需要重启 vscode 才能生效。
events 处理处理 React elements events 和处理 DOM elements 很相似，但有一些语法区别：React events 命名使用 camelCase 规则，而不是 lowercase使用 JSX 传入 function 作为 events handler，而不是 string 字符串HMTL 中处理 events 示例如下： Activate Lasers React 中示例如下： Activate Lasers 注意它们的区别之处一个是 event 名称，一个是 handler 定义方式。另一个区别是在 React 中不能通过 return false 的方式防止 events 的默认行为，需要明确的调用 preventDefault method 来实现。例如在一个 html 页面中定义一个 a tag 并取消其默认打开新页面的行为，实现如下： Click me React 中实现同样功能代码如下：class Link extends React.Component { constructor(props) { super(props); this.handleClick = this.handleClick.bind(this); } handleClick(e) { e.preventDefault(); console.log('clicked me'); } render() { return ( click me ) } }e 表示 synthetic 综合的 event，当前哪个 event 触发了 e 就表示哪一个。使用 bind 绑定 的 method 在调用时会自动将 e 传入 method。下面会对 bind 是什么作出解释。React events 同原生的 events 不完全相同，查看所有可用的 events 查看官方介绍：https://reactjs.org/docs/events.html在 React 中一般情况下不需要通过调用 addEventListener 来给 element 添加 event listener。直接在 element 初始化时为其设置 event listener 即可。当通过 class 来定义 component 时 event handler 一般是一个 class method，例如上面示例的 handleClick。下面的示例我们构建一个 Toggle component 可以让用户通过一个 button 来切换 ON/OFF 状态：const React = require('react') const ReactDOM = require('react-dom') class Toggle extends React.Component { constructor(props) { super(props); this.state = { isToggleOn: true }; this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState({ isToggleOn: !this.state.isToggleOn }); } render() { return ( { this.state.isToggleOn ? 'ON' : 'OFF'} ); } } ReactDOM.render(< Toggle />, document.getElementById('root') );在 JSX callback 中使用 this 需要特别注意，JavaScript class 的 methods 默认是相互隔离的，如果没有主动 bind 捆绑 method 到 this，在另一个 method 中使用 this.method 会报错 undefined。如果调用 method 时不写括号() 例如：onClick={this.handleClick} 则需要提前 bind 这个 method 到 this 中，如上面的示例，bind 语法如下：this.handleClick = this.handleClick.bind(this); 一般将其放在 constructor 中，这样初始化中就会自动执行，当然也可以在调用时直接定义： { this.state.isToggleOn ? 'ON' : 'OFF'} 如果不想使用 bind 语法来处理，那么还有两种方式来处理 class 中 methods 互相隔离这个问题。第一种叫做 class fields syntax 语法，通过使用 arrow function 的模式定义 method，这样就可以通过通过 this.method 的方法调用 method： constructor(props) { super(props); this.state = { isToggleOn: true }; } handleClick = () => { this.setState({ isToggleOn: !this.state.isToggleOn }); } render() { return ( { this.state.isToggleOn ? 'ON' : 'OFF'} ); }这样就不需要在 constructor 中定义 bind 同时可以在 callback 中直接调用 this.handleClick。但是需要注意目前这只是 React 实验性的语法，不一定保证以后会一直可用。第二种是在 callback 中通过 arrow function 的模式调用 method： handleClick() { this.setState({ isToggleOn: !this.state.isToggleOn }); } render() { return ( this.handleClick()} > { this.state.isToggleOn ? 'ON' : 'OFF'} ); }注意这种方法需要在 method 名称后加括号()。这种方法的缺点是当每次重新 render 渲染时都会创建新的 callback。当这个 callback 包含传给其 child component 的 props 时，可能会导致 child 重新被渲染。通常情况下推荐使用 constructor 定义 bind 或者使用 class fields syntax 语法来避免这些性能问题。给 event handler 传入数据有时候需要给 event handler 传入附加的参数，如下面示例 button 点击时输出一个输入数据到终端： handleClick(a, e) { this.setState({ isToggleOn: !this.state.isToggleOn }); console.log(e._reactName); console.log(a); } render() { return ( { this.state.isToggleOn ? 'ON' : 'OFF'} ); }上面的示例将字符串 aaa 作为 handleClick 的传入参数，并将 bind 绑定过程直接放在 callback 中，这样就不需要在 constructor 中进行 bind 定义了。通过 bind 绑定后会自动将 e：synthetic 综合的 event 作为第二个参数传入 function，e._reactName 返回 event 名称。上面的示例也可以通过 arrow function 在 callback 中定义实现： handleClick(a, e) { this.setState({ isToggleOn: !this.state.isToggleOn }); console.log(e._reactName); console.log(a); } render() { return ( this.handleClick('aaa', e)} > { this.state.isToggleOn ? 'ON' : 'OFF'} ); }上面的示例中 e 依然表示 synthetic event。两种方法都会将 e 作为第二个参数传入。在 arrow function 中我们可以清晰地看到数据的位置，但是通过 bind 的方式会将有些参数自动转发过去。
React 入门教程之四 -- rendering, components 和 state
rendering elements 渲染元素一个 element 表示我们想要显示在屏幕上的内容：const element = Hello, world ; 不同于浏览器 DOM 中的 elements，React elements 是简单的 objects 且可以很方便的创建，React DOM 会严格的刷新 DOM 并匹配对应的 React elements。容易混淆的概念是 component 和 element，区别是 component 是用来创建 element 的。在后续章节会介绍。在 DOM 中渲染元素我们的 html 页面中定义了一个 div 容器： 我们将其称作 root DOM 节点，因为它所有的内容都是被 React DOM 管理的。通常情况下使用 React 创建的程序只有一个 root DOM 节点。如果你是将 React 整合到现有网站中，你可以有任意个独立的 root DOM 节点。将 React elements 渲染到 root DOM 节点，需要通过调用 ReactDOM.render()，并将 React element 和 root DOM 节点作为传入参数：const element = Hello, world ; ReactDOM.render( element, document.getElementById('root') );此时页面会显示 hello world。刷新渲染的元素React element 是 immutable 不可改变的，当创建了一个 element 后不可以修改其 children 或 attributes，一个 element 就好像一个视频的一帧，它表示了某一时间点的 UI。从我们目前学到的知识，唯一刷新 UI 的方法就是重新创建新的 elements 然后调用 ReactDOM.render()，通过设置 setInterval 来定时刷新：const tick = () => { const element = {new Date().toLocaleTimeString()} ; ReactDOM.render( element, document.getElementById('root') ); } setInterval(tick, 1000);这样就会每秒钟创建一个新的 element 并通过 ReactDOM.render() 渲染到界面。通常情况下大多数 React app 只会调用 ReactDOM.render() 一次。下一章节会介绍如何将封装到 component 中。React 只会更新必要的内容React DOM 会比较其当前和上一个状态的，然后只对有了变化的部分进行更新来达到最终期望的状态。我们打开上面示例的运行页面，通过chrome 的开发工具查看 elements 情况，可以看到只有时间元素每秒在刷新：![2021-02-24T07:44:17.png][即使我们每秒钟都新建并渲染 element，但是只有时间文本 node 是一直通过 React DOM 在刷新的。通过以上的实验，思考我们的 UI 在某个时间点应该是什么样的，而不是只想这着去修改它。components 和 propscomponents 将 UI 元素分割为独立的，可复用的片段，每个片段都是单独存在的。这一章节介绍 component 的概念，更多细节参考：React.Componentcomponents 类似于 JavaScript 的 functions，它可以接受抽象的输入数据(props)，然后返回 React elements 用来在界面上显示。Function 和 Class Components最简单的定义 component 方式就是定义一个 JavaScript function：const Welcome = (props) => { return hello, {props.name} }上面的 function 是一个有效的 React component，因为它接受一个单参数 props object 作为传入数据并返回一个 React element。我们称这种 component 为 function component。也可以使用 ES6 的 class 定义 component：class Welcome extends React.Component { render() { return hello, {this.props.name} } }以上两种定义方式是一致的。需要注意的是 components 名称必须是以大写字母开头，因为 React 会见以小写字母开头的 components 作为 DOM tags 标签，如： 表示一个 html div 标签。rendering a component上面的介绍中，我们只遇到了 DOM tags 标签类型的 React elements，例如：const element = ; elements 也可以表示用户自定义的 components：const element = <Welcome name='marco' /> 当 React 检测到使用了用户自定义的 components 它会将此 JSX 内的 attributes 或 children 作为一个 object 传入 component，这个 object 叫做 props。下面的示例会输出 hello, marco:const Welcome = (props) => { return hello, {props.name} ; } const element = <Welcome name='marco' />; ReactDOM.render( element, document.getElementById('root') );以上示例过程如下：首先调用 ReactDOM.render() 渲染 <Welcome name="Sara" /> 元素.React 调用 Welcome component 使用 {name: 'Sara'} 作为 props.Welcome component 返回一个 Hello, Sara 元素.React DOM 高效的更新 DOM 来匹配 Hello, Sara 结果.构建 componentcomponent 可以在其输出中引入关联其他 components。这可以让我们在一个 component 内抽象出一个多层的结构。一个 button，一个 form，一个 dialog 或者一个 screen，在 React app 中他们都统称为 components。例如我们可以创建一个 App component 来渲染多个 Welcome component：const Welcome = (props) => { return hello, {props.name} ; } const App = () => { return ( <Welcome name='marco' /> <Welcome name='tim' /> <Welcome name='jone' /> ) } ReactDOM.render( <App />, document.getElementById('root') );以上示例中，没有定义 App 的 props，因为不需要给其传入数据，也是可以的。一般情况下，新建的 React app 只有一个顶层的 App component。拆解 component不要害怕将一个 component 拆解为多个小 components。例如下面这个 Comment component：function Comment(props) { return ( {props.author.name} {props.text} {formatDate(props.date)} ); }它的 props 包含一个 author object，一个 text，一个 data，描述了一个社交网站上一个 commit 的内容。修改这个 component 有点困难，因为它有很多的嵌套，同时也难以复用它的内部组件。下面我们尝试拆解这个 component。首先我们拆解出 Avatar：const Avatar = (props) => { return ( ); }Avatar 并不需要知道它被用于 commit 中，因此我们修改其 prop 名称为一个更加通用的：user。推荐从 component 本身为出发点命名 props，而不是考虑什么地方使用它。现在我们可以简化 Commit component：function Comment(props) { return ( <Avatar user={props.author} /> {props.author.name} {props.text} {formatDate(props.date)} ); }我们将 props.author 作为 user 数据传入 Avatar component 中。下面我们拆解 UserInfo，其中包含一个 Avatar component：const UserInfo = (props) => { return ( <Avatar user={props.user} /> {props.user.name} ); }然后进一步简化 Commit：function Comment(props) { return ( <UserInfo user={props.user} /> {props.text} {formatDate(props.date)} ); }拆解 component 在开始看起来使工作量变大了，但是在稍微复杂写的 app 中我们就能够利用这些可复用的 components。一条基本准则是：如果 UI 中的某一部分被多次使用，如 button，panel，Avatar等，或者其自身结构比较复杂，如：App, FeedStory, Comment 等，将他们拆解为独立 components 是一个好的选项。props 是只读的当把一个 component 定义为 function 或 class 时，需要注意的是不可以修改 props 的值。考虑下面的 function：function sum(a, b) { return a + b; }以上的 function 被称作 pure 纯粹的，以为它没有尝试修改输入数据。作为对比，下面的就是 impure 不纯粹的 function，因为会尝试修改它的输入数据：function sum(a, b) { a = b; }React 程序有一条限制条件：所有的 React components 都需要是 pure function 来对待 props 数据。当然应用程序的 UI 是随时间动态变化的。下一节我们会介绍 state 的概念。通过 state 可以使 React components 在运行期间修改它们的输出 elements 来响应用户动作，网络响应等。同时不违反上面的那条规则。state 和 lifecycle这一节介绍 React components 中 state 和 lifecycle 的概念。在前一章的示例中，我们通过一个 tick function 在指定时间间隔通过创建新 element 并渲染的方式刷新 UI：const tick = () => { const element = ( hello world {new Date().toLocaleTimeString()} ); ReactDOM.render( element, document.getElementById('root') ); } setInterval(tick, 1000);下面我们介绍通过创建一个封装好的 Clock component，设置定时器并更新其自身。首先我们根据上面的示例创建 Clock component：const Clock = (props) => { hello world {props.date.toLocaleTimeString()} } const tick = () => { ReactDOM.render( <Clock date={new Date()} />, document.getElementById('root') ); } setInterval(tick, 1000);在 tick 调用 Clock component 并定义 date prop 的数据供 Clock 使用。但是上面的实现缺乏一个基本需求，那就是 Clock 应该在其自身中定义定时器并每秒刷新数据的。我们想要在渲染时达到如下效果调用 Clock：ReactDOM.render( <Clock />, document.getElementById('root') );为了实现上述功能，需要为 Clock 添加 state。state 类似于 props 但是它是由 component 私有且完全控制的。首先我们需要将 component 转换为 class 模式，转换过程如下：首先创建一个 ES6 class，且继承自 React.Component。添加一个 render() function，将原 component function 的返回元素放入其返回值中在 render() 中用 this.props 代替 propsclass Clock extends React.Component { render() { return ( hello world {this.props.date.toLocaleTimeString()} ); } }当 update 更新发生时会自动调用 render function。但当我们将 <Clock /> 放入 DOM 后，将只会有一个 Clock object 实例被使用，这就让我们可以使用 state 或 lifecycle 等功能。添加 state下面我们将 date 数据直接放入 CLock component 中。首先将 render 中的 this.props.date 修改为 this.state.date：class Clock extends React.Component { render() { return ( hello world {this.state.date.toLocaleTimeString()} ); } }然后添加 class constructor 构造器给 this.state 赋初值：class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()} } render() { return ( hello world {this.state.date.toLocaleTimeString()} ); } }注意 class component 总是应该使用 constructor 且初始化参数为 props。child class 有 constructor 时需要调用 super 来初始化 parent class，具体语法参考我的 JavaScript 教程：https://blog.niekun.net/archives/2011.html然后删除渲染到 DOM 中 Clock 的 date prop，以及我们设置的 setInterval 定时器：ReactDOM.render( <Clock />, document.getElementById('root') );修改完成后的完整代码如下：class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()} } render() { return ( hello world {this.state.date.toLocaleTimeString()} ); } } ReactDOM.render( <Clock />, document.getElementById('root') );下面我们实现 Clock 设置自己的定时器并每秒更新。添加 lifecycle method对于包含很多 components 的程序，但某个 component 不再需要是需要及时释放其占用的资源。我们需要 Clock 第一次在 DOM 中渲染时设置一个 timer 定时器，在 React 中叫做 mounting 载入。同时我们需要当 Clock 在 DOM 中被删除时清除这个定时器，在 React 中叫做 unmounting 卸载。我们可以在 components 载入或载出时通过定义特殊的 method 来运行特定指令： componentDidMount() { } componentWillUnmount() { }这些 methods 叫做 lifecycle methods。componentDidMount method 会在 component 第一次输出到 DOM 后被自动调用，我们可以将定时器定义在这里： componentDidMount() { this.timerID = setInterval(() => this.tick(), 1000); }这样当 Clock 渲染到 UI 后会自动启动这个定时器。注意使用 this 定义的参数可以在 class 中任意地方被调用。注意 setInterval 中定义的响应动作需要写在 callback 内 () => {} 中，不要直接写：setInterval(this.tick, 1000)。因为如果要在 callback 调用 method 需要在 constructor 中做如下定义：this.tick = this.tick.bind(this); componentWillUnmount method 会在 component 将要被删除时自动调用，我们将定时器在这里取消： componentWillUnmount() { clearInterval(this.timerID); }接下来我们定义每秒都会自动运行的 tick method，通过 this.setState() 来更新本地 state 中的设置： tick() { this.setState({ date: new Date() }); }最终的完整代码如下：const React = require('react') const ReactDOM = require('react-dom') class Clock extends React.Component { constructor(props) { super(props); this.state = { date: new Date() } } componentDidMount() { this.timerID = setInterval(() => this.tick(), 1000); } componentWillUnmount() { clearInterval(this.timerID); } tick() { this.setState({ date: new Date() }); } render() { return ( hello world {this.state.date.toLocaleTimeString()} ); } } ReactDOM.render( <Clock />, document.getElementById('root') );现在整个处理流程如下：当 ReactDOM.render() 传入 <Clock /> 后，React 调用 Clock 构造器初始化 state 为一个包含 date 的 object然后 React 调用 Clock 的 render() 查询到需要显示的 UI 元素，然后更新 DOM 以匹配 Clock 的输出当 Clock 的输出嵌入到 DOM 后会调用 componentDidMount，Clock 告诉浏览器设置一个定时器每秒调用 tick()每秒钟调用一次 tick()，这里面 Clock component 通过 setState() 配置了其 UI 更新任务，通过 setState() React 就知道了 state 发生了变化并再次调用 render() 监测需要显示的内容，此时的 this.state.date 和上一次的发生了变化，React 就会更新 DOM 到最新的状态。当 Clock 从 DOM 中删除后，React 会调用 componentWillUnmount 并结束定时器正确使用 state关于 state 的使用需要如下的几点要求。第一点，不要直接修改 state。下面的语法不会触发重新 render 渲染 component：this.state.comment = 'Hello'; 正确的语法为使用 setState()：this.setState({comment: 'Hello'}); 唯一可以对 state 赋值的是在 constructor 构造器中。第二点，state 更新是异步的。React 为了性能可能会在一次 component update 更新中捆绑多个 setState() 调用，由于 this.props 和 this.state 可能会被异步更新，所以不要依赖他们的数据来计算后续的 state。如下示例可能会错误的更新 counter：this.setState({ counter: this.state.counter + this.props.increment, });为了实现是这个需求，使用 setState 的另一种格式：传入一个 function，第一个参数为当前 state，第二个参数为 props 然后内部计算 state 更新：this.setState(function (state, props) { return { counter: state.counter + props.increment }; });第三点，state 更新会合并。当调用 setState() 后，会合并设置的 object 到当前 state 中。如下示例，state 可能包含多个独立的变量： constructor(props) { super(props); this.state = { posts: [], comments: [] }; }然后我们可以单独调用 setState() 来分别更新它们： componentDidMount() { fetchPosts().then(response => { this.setState({ posts: response.posts }); }); fetchComments().then(response => { this.setState({ comments: response.comments }); }); }合并过程是自动完成的，所以通过 setState 修改 comments 只会更新 comments 而不会改变 posts。数据向下传递一个 component 的 child 或 parent 都不会知道当前 component 是包含 state 还是不包含，且不关心是通过 function 还是 class 方式构建的 component。所以 state 被认为是封装的不能够被外界所访问。component 的 state 可以作为 props 向它的 child component 传递：<FormattedDate date={this.state.date} /> 如上所示 FormattedDate 可以接受 date prop，它并不知道数据来自 parent 的 state 还是 props：function FormattedDate(props) { return It is {props.date.toLocaleTimeString()}. ; }通常这叫做 top-down 或 unidirectional 数据流。任何 state 都被某个特定 component 所有，state 的数据只能在其 component 的 child 中传递出去。我们通过建立 App component 并构建三个 Clock component 来展示 component 之间是互相独立的：function App() { return ( <Clock /> <Clock /> <Clock /> ); } ReactDOM.render( <App />, document.getElementById('root') );每个 Clock 都有个各自的定时器并独立更新。在 React 中，components 定义为 stateful 还是 stateless 的取决于其在运行中可能的变化，可以在 stateful 的 component 中使用 stateless 的 component，反过来亦可。
请看下面的一个定义：const element = Hello, world! ; 它既不是一个 string 也不是 html。它叫做 JSX，是一种对 JavaScript 语法的扩展。推荐在 React 中使用 JSX 来描述 UI，它用来创建 React 类型的 elements 然后将其在 DOM 中渲染。下面对 JSX 做一些基本介绍。使用 JSX 的原因React 认同这种观点：渲染逻辑结构和 UI 逻辑结构是原生相互耦合的，events 的处理，状态的变化，数据何时显示等。不同于人为的将 markup 部分和 logic 逻辑部分放在单独的文件，React 的耦合单元叫做 components 可以同时包含 markup 和 logic 部分。React 并不强制要求使用 JSX，但是大部分开发者认为它可以帮助在 JavaScript 中处理 UI 的问题。它也可以帮助 React 显示更多的 error 或 warning 信息。JSX 中使用 JavaScript 表达式下面的示例中，我们声明一个变量并在 JSX 中通过大括号{} 调用变量：const name = 'marco nie'; const element = hello, {name} ReactDOM.render( element, document.getElementById('root') );我们可以将任意的 JavaScript 表达式通过大括号{}放如 JSX 中。例如：1 + 1，user.name 都是有效的 JavaScript 表达式：const city = 'han zhong'; const user = { name: 'marco', age: 20 } const element = hello, {user.name}, from {city}, time: {1 + 20} 可以将 JSX 分割成多行以提高代码可读性，需要使用小括号包围起来，防止编译器错误的自动添加分行符：const element = ( hello, {user.name}, from {city}, time: {1 + 20} );编译后 JSX 表达式会转换为通用的 JavaScript function 或 objects。这就意味着我们可以在 if 或 for 表达式中，传入数据中或者 返回值中使用 JSX：const testFuc = () => { return hello, {user.name} }JSX 中定义属性可以使用引号 "" 将一个字符串作为属性：const el1 = 可以以通过大括号{} 将一个 JavaScript 表达式嵌入属性：const el2 = 当使用 JavaScript 嵌入属性时不能在大括号中使用引号，同一个属性中只能使用引号嵌入字符串或者大括号嵌入 JavaScript 表达式中的一种。由于 JSX 更加接近于 JavaScript 而不是 html，所以 React 中的 DOM 使用 camelCase 属性命名规范代替 html 中的属性名称，例如 html 元素的 class 属性在 JSX 中定义为 className，tabindex 在 JSX 中为 tabIndex。定义子元素如果一个 tag 元素内容是空的则可以使用 /> 立刻结束定义：const el3 = JSX tag 标签内也可以有子元素：const el4 = ( hello, i am your friend );注意只能在一个元素内定义子元素，不能直接定义两个同级的元素否则会报错。防止 injection 注入攻击在 JSX 中嵌入用户输入是安全的：const title = response.potentiallyMaliciousInput; // This is safe: const element = {title} ;默认情况下 React 在渲染前会将脱开所有嵌入 JSX 中的数据，因此可以确保不会注入任何没有明确定义在应用中的数据。渲染前所有的所有内容都会转换为 string 字符串形式。则能够防止 XSS (cross-site-scripting) 攻击。JSX 表达 objectsBabel 会将 JSX 向下编译为对 React.createElement() 的调用，以下两种定义方法是相同的：const el5 = ( hello world );const el6 = React.createElement( 'h1', {className: 'test'}, 'hello world' )通过 React.createElement() 创建元素会额外做一些语法检查来防止错误代码。但通常我们使用下面语法创建一个 object：const el7 = { type: 'h1', props: { className: 'test', children: 'hello world' } }以上方式创建的 object 叫做 React elements。React 使用这些 objects 来构建 DOM 并及时更新。推荐代码编辑器使用 Babel 语法定义环境，这样可以同时支持 ES6 和 JSX 语法结构。设置方法参考：https://babeljs.io/docs/en/editors
下面我们通过创建 Create React App 介绍 React 的基本语法。通过上一节介绍的方法创建一个新程序 my-app-1:npx create-react-app my-app-1 cd my-app-1关于创建新项目及可能出现的问题参考上一篇：https://blog.niekun.net/archives/2175.htmlCreate React App 官方教程：https://create-react-app.dev/docs/documentation-intro项目结构程序目录结构如下：my-app-1 ├── README.md ├── node_modules ├── package.json ├── .gitignore ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt └── src ├── App.css ├── App.js ├── App.test.js ├── index.css ├── index.js ├── logo.svg ├── serviceWorker.js └── setupTests.js对于一个项目而言，下面两个文件是必须的:public/index.html 是页面的模板文件;src/index.js 是 JavaScript 入口文件.其他文件可以根据需要添加，删除或重命名。可以在 src 目录下建立子目录，为了加快 build 编译项目的速度，只有在 src 目录下的文件才会被 webpack 处理。所以需要将所有的 js 和 css 文件放在 src 目录下，否则 webpack 打包器不会识别到它们。只有在 public 目录下的文件才会被 public/index.html 文件识别到。所以所有的项目文件都需要放在 public 和 src 目录下，与项目无关的文件可以放在上级路径内，例如项目说明文件等。可用脚本指令在项目目录下，可以执行如下命令。npm start开发模式下运行 app：npm start 打开 http://localhost:3000 可以查看效果。对代码作出修改后页面会自动重新加载。npm test交互模式下运行 test 运行器。更多介绍参考：https://create-react-app.dev/docs/running-testsnpm run build编译项目程序到 build 目录下，会自动将 React 转换为产品模式，并优化项目结构以提高性能。代码是经过 minified 最小化处理过的，文件名都包含 hashes 以在客户端能够及时识别更新。更多 build 参考：https://create-react-app.dev/docs/production-build清理项目默认使用的模板是 cra-template，包含一个基本的页面和 React component，我们先将项目清理掉不需要的内容。打开 public 目录下的 index.html 可以看到 body 块内定义了： index.html 内可以使用 %PUBLIC_URL% 表示 public 目录地址。删除 src 目录内所有文件，并新建 index.js 文件。运行程序：npm start 会自动在浏览器打开项目。hello world最简单的 React 示例如下，将代码加入 index.js 文件：const React = require('react') const ReactDOM = require('react-dom') ReactDOM.render( hello world , document.getElementById('root') );保存文件后刷新浏览器页面查看效果。
React 是一个用来创建用户界面的 JavaScript 库。它从一开始就被设计为 gradual adoption 渐进式融入的模式，你可以根据需求使用 React 到你的项目中，无论是使用 React 对你的 html 页面做简单交互，或者完全使用 React 搭建程序。对现有网页加入 React 支持当你需要对现有的网页做一些简单的交互动作时，React 也可以方便的加入进来。下面的示例中我们介绍如何通过 React 给页面添加一个 component 元素。首先给 html 页面添加一个 DOM 元素： 然后在 body 块结尾添加 React 模块的 script 脚本标签： 前两个 script 加载 React，最后一个是我们加载自定义 component 的代码。如果 script 部署在 CDN 上，推荐加上 crossorigin 属性可以提高加载速度。然后我们使用 React 编写 like_button.js，详细的实现代码我们在后续介绍，最后我们通过下面指令将 React component 加入 html 容器：// ... the starter code you pasted ... const domContainer = document.querySelector('#like_button_container'); ReactDOM.render(e(LikeButton), domContainer);以上就是将 React component 加入现有网页的过程。缩小 JavaScript 文件尺寸在部署我们的网站前，要注意没有经过缩小化处理的 JavaScript 脚本会导致页面加载缓慢。首先我们将加载 React 的脚本使用缩小化的版本链接： 对于我们自己编写的 React 脚本可以通过工具得到缩小后的版本，这里介绍通过 node.js 的 terser 库来实现：首先安装 node.js在项目目录下运行 npm init -y运行 npm install terser例如需要缩小 like_button.js，在终端运行：npx terser -c -m -o like_button.min.js -- like_button.js 就会在当前目录下得到缩小后的脚本文件 like_button.min.js。创建一个新的 React 程序以上介绍了如何将 React 添加到现有的 html 中，但是通过一套整合的工具链可以得到更好的用户和开发体验。React 团队推荐下面几种解决方案：如果你要学习 React 或者要创建一个单页面 app，推荐使用 Create React App如果你要使用 node.js 创建一个 server-rendered 服务器端渲染的网站，推荐使用 Next.js如果你要建立一个静态内容的网站，推荐使用 GatsbyCreate React AppCreate React App 是学习 React 很好的环境，也是建立单页面 React 应用程序最好的方式。官方网站：https://create-react-app.dev/它会自动设置开发环境是我们可以使用最新的 JavaScript 功能，提供很好的开发体验以及对发布时对程序进行优化。使用下面指令创建并运行一个新项目：npx create-react-app my-app cd my-app npm start注意如果你之前使用 npm install -g create-react-app全局安装过 create-react-app，推荐先卸载它 npm uninstall -g create-react-app 从而确保 npx 能够安装最新的版本。node.js 会自动安装 react, react-dom, 和 react-scripts with cra-template。通过 --template 选项可以自定义使用什么模板创建新程序，默认为：cra-template，模板可以在官网搜索：cra-template-*如果安装中报错，可以尝试更新 npm 以及清除缓存，然后再次尝试安装：npm i -g npm@latest npm cache clean -f 如果在本地开发推荐替换 npm 国内源可以提高下载速度，参考：https://blog.niekun.net/archives/2085.html创建 React App 不会处理后端的数据和逻辑，它只建立前端通道。所以你可以使用任何后端服务。通过 npm start 运行程序后，打开 http://localhost:3000/ 可以查看程序运行效果。当程序需要进行部署时，运行 npm run build 会在 build 目录下创建一个经过最小化代码优化处理的项目包，可以将其部署到服务器。Next.jsNext.js 是一个流行的 React framework 来创建 static 和 server‑rendered 程序。官网：https://nextjs.org/GatsbyGatsby 是通过 React 创建静态网站的最好的工具。它使我们可以使用 React components 但输出预渲染的 html 和 css 以确保页面加载速度。
TypeScript 是微软开发的开源编程语言，在全世界的开发者中流行。本质上来说 typescript 是 JavaScript 的超集，为其添加了很多功能。标志性的就是添加了 static type 静态类型定义，它可以声明期待的数据类型，例如：声明 function 中的期待的传入数据类型，期望的返回值的类型，object 的结构及 property 的数据类型等。typescript 是一个强大的工具，给 JavaScript 项目开启了新的世界。它使我们的代码更加的安全有力，在程序发布前就可以防止很多的 bug 出现。在编写代码期间就能够及时反映出问题点，并且它已经被整合在现代的编译器中，如 VS code。语法说明下面是一个 typescript 代码示例：type User = { name: string; age: number; }; function isAdult(user: User): boolean { return user.age >= 18; } const justine: User = { name: 'Justine', age: 23, }; const isJustineAnAdult: boolean = isAdult(justine);首先我们通过 type 关键词定义一个自定义的 object 类型 User，其中定义了 User 的两个 properties 及其数据类型。然后在定义的 function isAudlt 中接收一个参数，此参数类型为 User 类型，然后返回值类型为 boolean 类型。然后我们定义了 justine object 且其类型为 User。在其中定义的两个 properties 也符合 User 中的类型。最后定义了变量 isJustineAnAdult 且其数据类型为 boolean，将 isAudlt 的返回值赋给它。如果我们在编程中没有使用指定的数据类型，typescript 将会给出警告提醒我们错误的地方，比如上面的示例中修改 justine 的 age 为一个字符串：在 vs code 中会提示此处有错误。另一方面也并不需要将所有地方的数据类型都做出声明，typescript 会自动推断出需要的类型。例如：即使我们不给 isJustineAnAdult 声明 boolean 类型，typescript 也会自动推断它是 boolean 类型的。那么如何运行 typescript 代码呢？首先需要通过 npm 安装 typescript，它会安装我们需要的一些可执行程序：npm install typescript 下面我们可以通过 tsc 命令将 typescript 代码转换为标准 JavaScript 代码。我们将上面示例中的代码保存为 demo.ts 文件，然后通过 npx 调用 tsc 命令(如果以 global 方式安装 typescript 则可以直接运行命令)：npx tsc demo.ts 以上命令会转换 JavaScript 代码并自动创建一个 demo.js 文件，此文件可以在 node.js 运行:function isAdult(user) { return user.age >= 18; } var justine = { name: 'Justine', age: 23 }; var isJustineAnAdult = isAdult(justine);如果 typescript 代码中包含错误，则使用上面的转换命令会出现报错信息：% npx tsc demo.ts demo.ts:12:5 - error TS2322: Type 'string' is not assignable to type 'number'. 12 age: '23', ~~ demo.ts:3:5 3 age: number; ~~ The expected type comes from property 'age' which is declared here on type 'User' Found 1 error.可以看到 typescript 成功的防止将包含潜在错误代码的程序发布出去。更多介绍除此之外 typescript 也包含了很多其他很好的工具，如：interfaces, classes, utility types 等。更多使用方法参考 typescript 官方文档：https://www.typescriptlang.org/docs。typescript 在 node.js 生态中已经有了很好的发展，且被用于很多开源项目和框架中。以下是一些使用 typescript 的开源项目：NestJS - robust and fully-featured framework that makes creating scalable and well-architected systems easy and pleasantTypeORM - great ORM influenced by other well-known tools from other languages like Hibernate, Doctrine or Entity FrameworkPrisma - next-generation ORM featuring a declarative data model, generated migrations and fully type-safe database queriesRxJS - widely used library for reactive programming总结通过这 14 篇教程，我们介绍了 node.js 的基本语法和相关使用场景。后期可以在具体项目中进行更加详细的体验。
node.js 入门教程之十三 -- environment，error handler 和 log object
environment 运行环境production environment 产品环境和 development environment 开发环境可以设置不同的配置。node.js 默认为 development env 开发环境。通过设置环境变量：NODE_ENV=production 可以告诉 node.js 当前为产品环境。Linux 中可以通过如下指令修改：export NODE_ENV=production 这样在当前 shell 中就会以 production 模式运行 node 程序，如果要永久修改此环境变量可以将命令写入 ~/.bashrc 文件。也可以在运行 node 程序时定义 NODE_ENV：NODE_ENV=production node app.js 设置 NODE_ENV 为 production 有如下优势：日志记录保持在最小 level更多的 caching 缓存级别来提高运行效率可以通过状态判读符来判断当前运行环境，执行不同指令：if (process.env.NODE_ENV === 'development') { console.log('running on development environment') } if (process.env.NODE_ENV === 'production') { console.log('running on production environment') } if (['production', 'staging'].indexOf(process.env.NODE_ENV) != -1) { console.log('running on production environment') } else { console.log('running on development environment') }上面第三个指令通过调用 array 的 indexOf 来获取某个字符串在数组中的索引号，如果不存在这个字符串则返回 -1。注意使用类型判断符=== 是为了保证类型和数值都匹配。error handling 错误处理node.js 中通过 exceptions 来处理 error 情况。可以通过 throw 创建一个 exception：const test = 123; throw test;当 JavaScript 执行到上面的 throw 时程序会立刻停止，然后控制权会交给最近的 exception handler 例外控制器。JavaScript 中 test 可以是任意的字符串，数字或者 object。但在 node.js 中只用来 throw Error objects。一个 error object 可以是 Error 的实例或者是 Error class 的 child class 的实例：throw new Error('error') class childError extends Error { } throw new childError('error')下面介绍几种 exception handler 处理器。第一种是通过 try/catch 声明来处理，在 try 中定义任意 exception 以及对应的 catch 块来响应：try { const a = 1; if (a == 0) { throw 'abc' } else { throw 123 } } catch (e) { console.log(e) }catch 的参数 e 就是对应 throw 的数据。如果在程序执行中，如果某个 throw 的 exception 没有被 catch，程序就会立刻 crash。可以通过监听 process 的 uncaughtException event 事件来解决这个问题：process.on('uncaughtException', err => { console.log('there was a uncaught error: ', err.message) process.exit(1) }) throw new Error('error')第二种是 promise 中的 error handler。使用 promise 可以将多个操作过程组合成链，在链的最后处理 error 情况，下面是一个简单示例：let a = 0; let b = 0; const doSomething1 = new Promise((resolve, reject) => { if (a == 0) resolve('resolve 1') else reject('reject 1') }) const doSomething2 = (data) => { return new Promise((resolve, reject) => { if (b == 0) resolve(data + ' : resolve 2') else reject('reject 2') }) } doSomething1 .then(doSomething2) .then(res => console.log(res)) .catch(rej => console.log(rej))当 doSomething1 为 resolve 时会执行 doSomething2()，为 reject 时会直接进入 catch 块。同样的 doSomething2 返回的 promise 为 reject 时也会进入 catch 块。此时通过输出的信息可以知道是在哪一个 promise 链响应了 exception。第三种是 async/await 的 error handling。使用 async/await 也是通过 try/catch 来处理 exception 情况的：let a = 1; const demo = new Promise((resolve, reject) => { if (a == 0) { setTimeout(resolve, 1000, 'resolve'); } else { reject('reject') } }) const test = async() => { try { const data = await demo console.log(data) } catch (error) { console.log(error) } } test() //output: //reject当 demo promise 为 reject 时会响应 test() 中的 catch 块。日志输出 object当我们在浏览器中使用 console.log() 输出某个 object 时，会得到一个很好的效果：点击箭头可以展开日志，完整地展示 object 的 properties：在 node.js 中和浏览器有所不同，它是将 object 的内容以字符串的形式输出到终端或者日志文件中。对于简单的 object 来说没有什么问题，但是对于包含超过 2 层嵌套以上的 object 来说 node.js 就会放弃输出所有内容而用一个 [Object] 占位符表示剩余内容：const obj = { name: 'joe', age: 35, person1: { name: 'Tony', age: 50, person2: { name: 'Albert', age: 21, person3: { name: 'Peter', age: 23 } } } } console.log(obj)输出结果为：{ name: 'joe', age: 35, person1: { name: 'Tony', age: 50, person2: { name: 'Albert', age: 21, person3: [Object] } } }可以看到第三层的内容已经用 [Object] 取代。强制要求输出 object 所有内容的最好的方法就是使用 JSON.stringify()：console.log(JSON.stringify(obj, undefined, 2))输出如下：{ "name": "joe", "age": 35, "person1": { "name": "Tony", "age": 50, "person2": { "name": "Albert", "age": 21, "person3": { "name": "Peter", "age": 23 } } } }第一个参数定义需要输出的 object，第二个参数是定义一个 function 用来转换字符串内容也可以不定义，第三个是定义缩进量。
Streams 是驱动 node.js 程序的核心概念。它提供了处理对文件的读写，网络传输，或者其他端到端的数据交换的更加高效的方式。streams 流并不是 node.js 首先引入的概念，unix 操作系统在很久之前就在使用了，一个程序可以通过 pipe 管道操作符 | 来传递 streams 流给其他程序。下面的示例是在 Linux 中，通过 pipe 管道将 cat 读取的文件数据传递给 grep 进行过滤，test.txt 文件内容如下：aaa bbb bbb ccc aaa ccc$ cat test.txt | grep aaa aaa bbb aaa cccstream 优点在传统方法中，当程序读取一个文件内容时会先将文件内容全部读取到内存中，然后再去使用它。而使用 stream 时会一段段的读取文件内容并进行处理，而不需要整体读取到内存。node.js 的 stream 模块是构建所有 streaming API 的底层。所有的 streams 流都是 eventEmitter 的实例。每种类型的 stream 都有各自的 event 事件，在数据流变化时触发对应事件。相比于其他数据处理方法，streams 有两个优势：内存效率：在使用数据前不需要将大量数据存入内存中。时间效率：能够更快的进入数据处理阶段，在获取到文件后能够很快的使用它而不需要等待它全部加载完毕。使用方法下面我们从一个示例来说明 stream 的使用方法。我们建立一个 http server，当收到请求时读取一个本地文件的内容并作为 response 发送给客户端。首先传统实现方式如下：const http = require('http') const fs = require('fs') const hostname = '127.0.0.1' const port = 3000 const server = http.createServer((req, res) => { fs.readFile(__dirname + '/test.txt', (err, data) => { if (err) { console.log('read error') } res.end(data) }) }) server.listen(port, hostname, () => { console.log(`Server running at http://${hostname}:${port}/`) })以上方式在收到请求后，通过 readFile() 读取文件内容到内存中，当成功后会调用 callback 通过 res.end(data) 发送读取的数据给客户端。如果文件较大时以上过程会花费一些时间。__dirname 返回当前执行文件的路径。下面介绍使用 stream 实现上面的过程：const http = require('http') const fs = require('fs') const hostname = '127.0.0.1' const port = 3000 const server = http.createServer((req, res) => { const stream = fs.createReadStream(__dirname + '/test.txt') stream.pipe(res) }) server.listen(port, hostname, () => { console.log(`Server running at http://${hostname}:${port}/`) })通过 fs.createReadStream() 返回一个文件的 stream。不同于第一种，以上方式会在 stream 中只要有了 data chunk 块就立刻作为 response 数据传输给客户端。这样就会一边读取文件一边传输数据。pipe()以上示例中调用 stream 的 pipe() method。它的功能是建立一个 source stream 源流到 destination stream 目标流的 pipe 管道。这样文件的 stream 流通向了 http response。pipe() 的返回是 destination stream 目标流，这样就可以很方便的链接多个 pipe：src.pipe(dest1).pipe(dest2)以下写法和上面示例效果一样：src.pipe(dest1) dest1.pipe(dest2)基于 stream 的 node.js API由于 stream 的巨大优势，很多 node.js 核心模块提供了 stream 的原生支持，以下是常用的部分：process.stdin 返回一个链接到 stdin 的 streamprocess.stdout 返回一个链接到 stdout 的 streamprocess.stderr 返回一个链接到 stderr 的 streamfs.createReadStream() 创建一个文件的可读的 streamfs.createWriteStream() 创建一个文件的可写的 streamnet.connect() 初始化一个基于 stream 的连接http.request() 返回一个 http.ClientRequest 实例，它是一个可写的 streamstreams 的类型有四个 streams 的 calsses：Readable: 一个可以作为管道源头的 stream，不能作为管道的目标也就是不能写入数据Writable: 一个可以作为管道目标的 stream，不能作为管道源头也就是不能从中获取数据Duplex: 一个既可作为管道源头也可以作为目标的 streamTransform: 类似于 Duplex创建 readable stream从 stream 模块定义一个可读的 stream 然后通过定义 readable._read() method 内容完成初始化：const Stream = require('stream') const readableStream = new Stream.Readable() readableStream._read = () => {}初始化也可以这样写：const readableStream = new Stream.Readable({ read() {} })以上就创建了一个可读的 stream，可以给其传输数据：readableStream.push('hi') readableStream.push('hello')可以将这个 stream 和一个可写的 stream 之间建立管道：readableStream.pipe(process.stdout) 创建 writable stream通过实例化一个 Writable object 然后通过定义 _write() method 内容完成初始化：const Stream = require('stream') const writeableStream = new Stream.Writable() writeableStream._write = (chunk, encoding, callback) => { console.log(chunk.toString()) callback() }接收到的数据块传入 chunk，encoding 定义数据类型，callback function 是当一个 chunk 数据块传输完成后调用，此 callback 可在 writeableStream.write 内定义，一般可以是 error 处理，下面会介绍。可以将定义的可写 stream 同一个可读 stream pipe 管道连接：process.stdin.pipe(writeableStream) 这样就可以在 stdin 和 writeableStream 间建立 pipe，此时在 stdin 输入数据就会立刻将数据显示在终端。从可读的 stream 读取数据下面的示例中，我们建立一个可读的 stream 和一个 可写的 stream，并建立 pipe 管道连接：const Stream = require('stream') const readableStream = new Stream.Readable() readableStream._read = () => {} const writeableStream = new Stream.Writable() writeableStream._write = (chunk, encoding, callback) => { console.log(chunk.toString()) callback() } readableStream.pipe(writeableStream) readableStream.push('hi\n') readableStream.push('hello\n')也通过 readable event 事件来处理可读 stream。当 stream 中有准备好的数据块时会触发 readable event：readableStream.on('readable', () => { console.log(readableStream.read().toString()) })给可写的 stream 写入数据使用 write method 来写入数据：writeableStream.write('hello world\n') 可以定义写入数据的编码格式和 callback function：writeableStream.write('hello world\n', 'utf-8', err => {console.log(err)}) 实际中 callback function 是否会执行要看 stream 初始化中在 writeableStream._write 是否调用了 callback。如果数据写入已经完成，可以使用 end method 来告诉可写的 stream：const Stream = require('stream') const writeableStream = new Stream.Writable() writeableStream._write = (chunk, encoding, next) => { console.log(chunk.toString()) next() } process.stdin.pipe(writeableStream) writeableStream.write('hello world\n') writeableStream.end()以上示例中如果在最后一句不调用 end method 则 writeableStream 会持续保持接收来自 stdin 的数据状态。创建 transform stream和可写的 stream 创建方式类似，可读也可写：const Stream = require('stream') const transformStream = new Stream.Transform() transformStream._transform = (chunk, encoding, callback) => { console.log('transform' + chunk.toString()) transformStream.push(chunk) callback() } const writeableStream = new Stream.Writable() writeableStream._write = (chunk, encoding, callback) => { console.log('write' + chunk.toString()) callback() } process.stdin.pipe(transformStream).pipe(writeableStream)在 transformStream._transform 定义了当 transform stream 接收到数据后通过 transformStream.push 将数据发到 readable stream 中，这样其他 stream 就可以读取到它接收到的数据了。更多 stream 使用方法参考：https://nodejs.org/api/stream.html
一个 buffer 缓存就是一个内存空间。JavaScript 开发者相较于 c++/go 等语言的开发者对内存的理解比较少。它表示在 JavaScript V8 engine 之外的分配的一块 fixed-size 固定大小的内存。可以想象 buffer 就是一个 integer 整型数组，每个元素代表一个 byte 的数据。node.js 中 buffer 通过 Buffer class 实现。为何需要 buffer引入 Buffer 是为了帮助开发者处理 binary 二进制数据。传统的 ecosystem 只可以处理 strings 字符串数据。Buffer 和 streams 是紧密关联的，当 stream processor 流处理器接收数据的速度大于其处理速度时，就会先将数据放在 buffer 中。可以想象下，在我们看在线视频时，在网络较好的情况下进度条中可以看到视频下载的进度是超过当前播放进度的，此时浏览器就会先 buffer 缓存数据后续按时间顺序播放出来。创建 buffer可以通过 Buffer.from(), Buffer.alloc() 和 Buffer.allocUnsafe() methods 来创建一个 buffer，下面依次介绍。使用 Buffer.from() 可以从 array 数组创建 buffer：const buf1 = Buffer.from('hey!') 使用 Buffer.from(string[, encoding]) 可以设置 string 的编码类型，Default: utf8：const buf2 = Buffer.from('7468697320697320612074c3a97374', 'hex') 也可以根据另一个 buffer：const buf3 = Buffer.from(buf1) 通过 Buffer.alloc() 或 Buffer.allocUnsafe() 来初始化一个指定大小的 buffer，单位为 byte 字节：const buf4 = Buffer.alloc(1024) const buf5 = Buffer.allocUnsafe(1024) 以上会创建一个 1kb 大小的 buffer。通过 alloc 创建的 buffer 会初始化其数据为 0，而 allocUnsafe 创建的 buffer 内的数据会是 uninitiated 未初始化的，这就表示使用 allocUnsafe 比 alloc 创建速度更快。但是通过 allocUnsafe 创建的 buffer 内存空间会包含有以前的数据可能引起潜在问题。使用 buffer我们可以像访问数组一样访问 buffer 数据：const buf1 = Buffer.from('hey!') console.log(buf1[0]) console.log(buf1[1]) console.log(buf1[2]) //output: //104 //101 //121这些输出的数字就是代表 buffer 中每个字符的 Unicode Code，h => 104, e => 101, y => 121。使用 toString() method 可以将 buffer 输出为字符：console.log(buf1) console.log(buf1.toString()) //output: //<Buffer 68 65 79 21> //hey!buf1.length 可以读取 buffer 长度：console.log(buf1.length) //output: //4也可以使用循环 iterate buffer 的数据：for (const iterator of buf1) { console.log(iterator) } //output: 104 101 121 33通过 write() method 可以给 buffer 写入数据：const buf6 = Buffer.alloc(5) buf6.write('hello')也可以像数组一样修改某个元素的数据：const buf6 = Buffer.alloc(5) buf6.write('hello') buf6[0] = 111 console.log(buf6.toString()) //output: //oello可以看到需要修改字符对应的 Unicode Code。使用 copy() method 可以复制 buffer 数据到其他 buffer：const buf1 = Buffer.from('hey!') const buf7 = Buffer.alloc(4) buf1.copy(buf7) console.log(buf7.toString()) //output: //hey!注意是源 buffer 掉用 copy method 来复制数据到目标 buffer。默认情况下会复制 buffer 的整个数据， copy method 可以设置其他三个参数来自定义目标 buffer 起始接收数据位置，源 buffer 起始复制数据位置，以及复制的新 buffer 的长度：const buf8 = Buffer.alloc(2) buf1.copy(buf8, 0, 0, 2) console.log(buf8.toString()) //output: //he可以使用 slice method 来选取 buffer 的一个片段，它和 copy 的不同是 slice 获取的 buffer 片段仍然和源 buffer 相关联，修改源 buffer 会改变片段中的 buffer 数据。slice 的第一个参数定义起始位置，第二个参数是选项，可以定义终止位置：const buf1 = Buffer.from('hey!') const slice = buf1.slice(0, 2) console.log(slice.toString()) buf1[0] = 111 console.log(slice.toString()) //output: //he //oe以上就是 Buffer 的简单使用方法。