文章目录

  • 1. React介绍和特点
    • 1.1 React是什么
    • 1.2 React的特点
  • 2. React的开发依赖
    • 2.1 React的三个依赖
    • 2.2 Babel和React的关系
    • 2.3 React的依赖引入
  • 3. React 入门案例
    • 3.1 入门案例
    • 3.2 组件化开发
      • 3.2.1 封装组件
      • 3.2.2 数据依赖
      • 3.2.3 事件绑定
    • 3.4 案例练习
    • 3.5 VSCode 代码片段
  • 5. JSX
    • 5.1 认识JSX
    • 5.2 为什么 React 选择了 JSX
    • 5.3 JSX 书写规范
    • 5.4 JSX的使用
      • 5.4.1 JSX中的注释
      • 5.4.2 JSX嵌入
      • 5.4.3 JSX绑定属性
    • 5.5 React 事件绑定
      • 5.5.1 事件绑定
      • 5.5.2 this 的绑定问题
      • 5.5.3 事件参数传递
    • 5.6 条件渲染
    • 5.7 列表渲染
      • 5.7.1 React的列表渲染
      • 5.7.2 列表中的key
    • 5.8 JSX的本质
      • 5.8.1 了解JSX的语法糖
      • 5.8.2 查看 Babel 对jsx的解析
      • 5.8.3 编写 React.createElement
      • 5.8.4 虚拟DOM的创建过程
      • 5.8.5 声明式编程
  • 6. 脚手架&创建项目
    • 6.1 前端工程的复杂化
    • 6.2 脚手架概念
      • 6.2.1 前端脚手架
      • 6.2.2 安装React脚手架
    • 6.2 创建 React 项目
    • 6.3 目录结构分析
    • 6.4 PWA(了解)
    • 6.5 脚手架中的webpack
    • 6.6 从零编写结构
      • 6.6.1 删除文件结构
      • 6.6.2 编写代码

1. React介绍和特点

1.1 React是什么

  • React:用于构建用户界面的 JavaScript 库;
  • React的官网文档:https://zh-hans.reactjs.org/

1.2 React的特点

1、声明式编程

  • 声明式编程是目前整个大前端开发的模式:Vue、React、Flutter、SwiftUI;
  • 它允许我们只需要维护自己的状态,当状态改变时,React可以根据最新的状态去渲染我们的UI界面

2、组件化开发

  • 组件化开发页面是目前前端的流行趋势,我们会将复杂的界面拆分成一个个小的组件;
  • 如何合理的进行组件的划分和设计也是后面学习的一个重点;

3、多平台适配

  • 2013年,React发布之初主要是开发Web页面
  • 2015年,Facebook推出了ReactNative,用于开发移动端跨平台;(虽然目前Flutter非常火爆,但是还是有很多公司在使用 ReactNative);
  • 2017年,Facebook推出ReactVR,用于开发虚拟现实Web应用程序

2. React的开发依赖

2.1 React的三个依赖

开发React必须依赖三个库:

  • react:包含react所必须的核心代码
  • react-dom:react渲染在不同平台所需要的核心代码
  • babel:将jsx转换成React代码的工具

对于Vue来说,我们只是依赖一个vue.js文件即可,但是react要依赖三个包;这三个库是各司其职的,目的就是让每一个库只单纯做自己的事情;

为什么要进行拆分呢?原因就是react-native。

  • react包中包含了 react webreact-native 所共同拥有的核心代码
  • react-dom针对webnative所完成的事情不同:
    • web端:react-dom会将jsx最终渲染成真实的DOM,显示在浏览器中
    • native端:react-dom会将jsx最终渲染成原生的控件(比如Android中的Button,iOS中的UIButton)。

2.2 Babel和React的关系

babel是什么呢?

  • Babel ,又名 Babel.js;是目前前端使用非常广泛的编译器、转移器。
  • 比如当下很多浏览器并不支持ES6的语法,但开发者希望使用ES6的简洁、强大的语法去开发项目。
  • 那么编写源码时我们就可以使用ES6来编写,之后通过Babel工具,将ES6转成大多数浏览器都支持的ES5的语法。

React和Babel的关系:

  • 默认情况下开发React其实可以不使用babel。
  • 但是前提是我们自己使用 React.createElement 来编写源代码,但它编写的代码非常的繁琐,可读性差。
  • 那么我们就可以直接编写 jsx(JavaScript XML)的语法,并且让babel帮助我们转换成 React.createElement 。

2.3 React的依赖引入

在编写React代码时,上述三个依赖都是必不可少的;引入方式:

  • 方式一:直接CDN引入
  • 方式二:下载后,添加本地依赖
  • 方式三:通过npm管理(后续脚手架再使用)
<!-- 在编写react的代码前引入-->
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

这里的 crossorigin 的属性,目的是为了拿到跨域脚本的错误信息

3. React 入门案例

3.1 入门案例

快速体验React

【案例一】需求:在界面上通过 React 显示一个 hello world(关注非注释代码即可)

<!-- 以下为html文件,省略了非关键代码-->
<body><div id="root"></div><!-- 添加依赖 三个包 采用CDN引入 --><script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script><script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script><script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script><script type="text/babel">// 编写React代码(jsx语法)// jsx 语法 -> 普通的javascript代码 -> babel// 渲染 hello world// React18之前用:ReactDOM.render// ReactDOM.render(<h2>hello world</h2>, document.querySelector("#root"));// react18之后:const message = "hello world"const root = ReactDOM.createRoot(document.querySelector("#root")); // 创建根元素root.render(<h2>{message}</h2>); // 在根元素上渲染内容// 可以如下创建多个根 ,但一般是一个// const app = ReactDOM.createRoot(document.querySelector("#app"));// app.render(<h2>hello react</h2>);</script>
</body>
  • 注意:这里我们编写 React 的 script 代码中,必须添加 type="text/babel",作用是可以让babel解析jsx的语法

必要说明:

  • ReactDOM.createRoot 函数:用于创建一个 React 根,之后渲染的内容会包含在这个根中

    • 参数:将渲染的内容,挂载到哪一个HTML元素上(如上已经提定义一个 id 为app 的 div)
  • root.render 函数:
    • 参数:要渲染的根组件
  • 可以通过{}语法来引入外部的变量或者表达式

【案例二】:需求:封装 render 函数,点击按钮改变文本 (为了简洁后续代码省略引入的依赖)

<script type="text/babel">const root = ReactDOM.createRoot(document.querySelector("#root"));// 1. 将文本定义为变量let message = "hello world";// 2. 监听按钮的点击function btnClick() {// 2.1 修改数据message = "hello react";// 2.2 重新渲染界面rootRender();}// 3. 封装一个渲染函数function rootRender() {root.render(<div><h2>{message}</h2><button onClick={btnClick}>点击修改文本</button></div>);}// 先调用一次rootRender();
</script>

3.2 组件化开发

3.2.1 封装组件

上面的案例整个逻辑其实可以看做一个整体,那么我们就可以将其封装成一个组件:

  • 基于 root.render 参数是一个HTML元素或者一个组件
  • 所以可以先将之前的业务逻辑封装到一个组件中,然后传入到ReactDOM.render 函数中的第一个参数;

在React中,这里暂时使用类的方式封装组件,步骤:

  • 1.定义一个类 继承自React.Component (类名大写,组件的名称是必须大写的,小写会被认为是HTML元素)
  • 2.实现当前组件的 render 函数 (render当中返回的 jsx 内容,就是之后 React 会帮助我们渲染的内容)
// 定义App根组件
class App extends React.Component {render() {return <h2>hello world</h2>;}
}
// 创建root并渲染 App根组件
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<App />);

3.2.2 数据依赖

组件化问题一:数据在哪里定义

在组件中的数据可以分成两类:

  • 参与界面更新的数据:当数据变时,需要更新组件渲染的内容;
  • 不参与界面更新的数据:当数据变时,不需要更新将组建渲染的内容;

参与界面更新的数据我们也可以称之为是参与数据流,这个数据是定义在当前对象state

  • 可以通过在构造函数this.state = {定义的数据}
  • 当数据发生变化时,我们可以调用 this.setState 来更新数据,并且通知React进行update操作;
  • 在进行update操作时,就会重新调用render函数,并且使用最新的数据,来渲染界面
// 定义App根组件
class App extends React.Component {constructor() {super();this.state = {message: "hello world",};}render() {const { message } = this.state;return <h2>{message}</h2>;}
}

3.2.3 事件绑定

组件化问题二:事件绑定中的this

在类中直接定义一个函数,并且将这个函数绑定到元素onClick事件上,当前这个函数的this指向的是谁呢?

默认情况下竟是 undefined,解释:

  • 在正常的DOM操作中,监听点击,监听函数中的this其实是节点对象(比如说是button对象);
  • 这次因为 React 并不是直接渲染成真实的DOM,我们所编写的 button 只是一个语法糖,它的本质React的Element对象;
  • 那么在这里发生监听的时候,react在执行函数时并没有绑定this,默认情况下就是一个undefined;

我们在绑定的函数中,可能想要使用当前对象,比如执行 this.setState 函数,就必须拿到当前对象的 this

  • 我们需要在传入函数时,手动给这个函数直接绑定this
  • 类似于下面的写法: <button onClick={this.changeText.bind(this)}>改变文本</button>
// 使用组件进行重构
// 类组件 / 函数式组件(下面为类组件)
// 类App 必须继承 React.Component 才能成为组件
class App extends React.Component {// 组件数据constructor() {super();this.state = {message: "hello world",};// 对需要绑定的方法,提前绑定好 this// 若这里绑定了,在下面render中的button中就不需要绑定了//this.btnClick = this.btnClick.bind(this);}// 组件方法(实例方法)btnClick() {console.log("btn", this);// react内部做了监视:1.将 state中message的值修改掉 2. 自动执行render函数this.setState({message: "hello react",});}// 渲染内容 必须 实现render方法render() {return (<div><h2>{this.state.message}</h2><button onClick={this.btnClick.bind(this)}>修改文本</button></div>);}
}

3.4 案例练习

【案例一:电影列表展示】(关注列表渲染形式)

<body><!-- 结构根元素 --><div id="root"></div><!-- 这里在本地引入react三个依赖--><script src="../lib/react.js"></script><script src="../lib/react-dom.js"></script><script src="../lib/babel.js"></script><script type="text/babel">// 1. 创建 rootconst root = ReactDOM.createRoot(document.querySelector("#root"));// 2. 封装 App组件class App extends React.Component {constructor() {super();this.state = {movies: ["电影a", "电影b", "电影c"],};}render() {// 1. 对 movies进行for循环// const liEls = [];// for (let i = 0; i < this.state.movies.length; i++) {//   const movie = this.state.movies[i];//   const liEl = <li>{movie}</li>;//   liEls.push(liEl);// }// 2. 使用 map// const liEls = this.state.movies.map((item) => <li>{item}</li>);return (<div><h2>电影列表</h2><ul>{this.state.movies.map((item) => <li>{item}</li>)}</ul></div>);}}// 3.渲染根组件root.render(<App />);</script>
</body>

【案例二:计数器案例】

// 定义App根组件
class App extends React.Component {constructor() {super();this.state = {counter: 100,};}render() {const { counter } = this.state;return (<div><h2>当前计数:{counter}</h2><button onClick={this.increment.bind(this)}>+1</button><button onClick={this.decrement.bind(this)}>-1</button></div>);}increment() {this.setState({counter: this.state.counter + 1,});}decrement() {this.setState({counter: this.state.counter - 1,});}
}
// 创建 root 并渲染 App 根组件
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<App />);

3.5 VSCode 代码片段

  • 在练习 React 的过程中,有些代码片段是需要经常写的,在VSCode中可以生成一个代码片段,方便我们快速 生成。

  • VSCode中的代码片段有固定的格式,所以一般会借助于一个在线工具来完成。具体的步骤如下:

    • 第一步,复制自己需要生成代码片段的代码;
    • 第二步,https://snippet-generator.app/ 在该网站中生成代码片段;
    • 第三步,在 VSCode 中配置代码片段;
  • 推荐代码提示 VSCode 插件: ES7+ React/Redux/React-Native snippets

5. JSX

5.1 认识JSX

// 1. 定义组件
const element = <h1>Hello, world!</h1>;
// 2. 渲染组件
const root = ReactDOM.createRoot(document.querySelector("#root"))
root.render(element)

这段element变量的声明右侧赋值的标签语法是什么呢?

  • 它不是一段字符串(因为没有使用引号包裹);
  • 它看起来是一段HTML元素,但 js 中并不能直接给一个变量赋值 html元素
  • 如果我们将 script 标签中的 type="text/babel" 去除掉,那么就会出现语法错误;
  • 其实它是一段jsx的语法;

JSX是什么?

  • JSX是一种 JavaScript 的语法扩展(eXtension),也在很多地方称之为JavaScript XML,因为看起就是一段XML语法;
  • 它用于描述我们的UI界面,并且其可以和JavaScript融合在一起使用;
  • 它不同于Vue中的模块语法,你不需要专门学习模块语法中的一些指令(比如v-for、v-if、v-else、v-bind);

5.2 为什么 React 选择了 JSX

React 认为渲染逻辑本质上与其他UI逻辑存在内在耦合

  • 比如UI需要绑定事件(button、a原生等等)
  • 比如UI中需要展示数据状态
  • 比如在某些状态发生改变时,又需要改变UI

他们之间是密不可分,所以React没有将标记分离到不同的文件中,而是将它们组合到了一起,这个地方就是组件(Component)

在这里,我们只需要知道,JSX 其实是嵌入到 JavaScript 中的一种结构语法;

5.3 JSX 书写规范

  • JSX 的顶层只能有一个根元素,所以我们很多时候会在外层包裹一个div元素(或者使用后面学习的 Fragment
  • 为了方便阅读,通常在 jsx 的外层包裹一个小括号(),这样可以方便阅读,并且jsx可以进行换行书写
  • JSX中的标签可以是单标签,也可以是双标签(如果是单标签,必须以/>结尾)

5.4 JSX的使用

5.4.1 JSX中的注释

在 JSX 结构中使用 {/**/} 添加注释

render() {const { message } = this.state;const element = <div>{/*JSX的注释*/}</div>;return (<div>{/*JSX的注释写法*/}<h2>{message}</h2></div>);
}

5.4.2 JSX嵌入

JSX 嵌入变量作为子元素

  • 情况一:当变量是Number、String、Array类型时,可以直接显示
  • 情况二:当变量是null、undefined、Boolean类型时,内容为空;
    • 如果希望可以显示null、undefined、Boolean,那么需要转成字符串;
    • 转换的方式有如 toString 方法、和空字符串拼接,String(变量)等方式;
  • 情况三:Object对象类型不能作为子元素(否则报错 not valid as a React child

JSX嵌入表达式

  • 运算表达式
  • 三元运算符
  • 执行一个函数
// 定义App根组件
class App extends React.Component {constructor() {super();this.state = {counter: 100,message: "hello world",names: ["a", "b", "c"],u: undefined,n: null,t: true,obj: {name: "foo",},};}render() {const { counter, message, names } = this.state;const { u, n, t, obj } = this.state;const res = counter > 99 ? "counter大于99" : "counter小于99";// 返回jsx内容return (<div>{/* 1. Number/String/Array 直接显示*/}<h2>{counter}</h2><h2>{message}</h2><h2>{names}</h2>{/* 2. undefined / null /Bollean 内容为空  */}<h2>{u}</h2><h2>{n}</h2><h2>{t}</h2>{/* 3. Object 类型不能作为子元素进行显示*/}{/*<h2>{obj}</h2>*/}{/* 4. 可以插入js的表达式*/}<h2>{name[0] + name[1]}</h2>{/* 5.可以插入三元运算符*/}<h2>{counter > 99 ? "counter大于99" : "counter小于99"}</h2><h2>{res}</h2>{/* 6. 可以调用方法*/}<h2>{this.toUpper()}</h2></div>);}toUpper() {return this.state.names.map((item) => item.toUpperCase());}
}
// 创建 root 并渲染 App 根组件
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<App />);

5.4.3 JSX绑定属性

  • 比如元素都会有 title 属性
  • 比如img元素会有 src 属性
  • 比如a元素会有 href 属性
  • 比如元素可能需要绑定 class
  • 比如原生使用内联样式 style

【下面例子还展示了动态绑定class的多种方式】

class App extends React.Component {constructor() {super();this.state = {message: "hello world",title: "我是h2",imgUrl:"https://t7.baidu.com/it/u=1595072465,3644073269&fm=193&f=GIF",isActive: true,objStyle: { color: "red", fontSize: "30px" },};}render() {const { message, title, imgUrl, isActive, objStyle } = this.state;// isActive 为true 就为 h2 加上 active 类名// class 绑定方式一:字符串拼接const className = `aa bb ${isActive ? "active" : ""}`; // 在外面拼接好// class 绑定方式二: 将所有class放入数组,const classList = ["aa", "bb"];if (isActive) classList.push("active");// class 绑定方式三: 第三方库 classnames(放后面说)return (<div>{/* 绑定全局title属性*/}<h2 title="我是h2">{message}</h2><h2 title={title}>{message}</h2>{/*<img src={imgUrl} alt="" />*/}{/* 2. 绑定class属性:最好使用className,不用class;因为在js中class是关键字 */}<h2 className="aaa"></h2><h2 className={className}>类名有{className}</h2><h2 className={classList.join(" ")}>类名有{classList}</h2>{/* 3. 绑定style属性 绑定对象类型,若原生样式属性名有横杠 - 则替换为驼峰写法 */}<h2 style={{ color: "red", fontSize: "30px" }}>hello</h2><h2 style={objStyle}>hello</h2></div>);}
}
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<App />);

5.5 React 事件绑定

5.5.1 事件绑定

回顾原生DOM的监听事件(如点击):

  • 方式一:获取DOM原生,添加监听事件;
  • 方式二:在HTML原生中,直接绑定onclick;

React 中的事件监听,这里主要有两点不同

  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写;
  • 我们需要通过{}传入一个事件处理函数,这个函数会在事件发生时被执行
// 省略其他代码...
btnClick() {console.log('触发点击了');
}
render(){return (<button onClick={this.btnClick}>按钮</button>)
}

5.5.2 this 的绑定问题

在事件执行后,我们可能需要获取当前类的对象中相关的属性,这个时候需要用到 this;如果我们这里直接打印 this,会发现它是一个 undefined

btnClick() {console.log('触发点击了',this); // this为 undefined
}

为什么是undefined

  • btnClick 函数并不是我们主动调用的,而且当 button发生改变时,React内部调用了btnClick函数
  • 而它内部调用时,并不知道要如何绑定正确的 this;

如何解决 this 的问题呢?

  • 方案一:通过 bindbtnClick 显示绑定 this
  • 方案二:使用 ES6 class fields 语法
  • 方案三:事件监听时传入箭头函数(推荐)
class App extends React.Component {constructor() {super();this.state = {counter: 0,};// 可以在这里提前绑定好this.btn1Click = this.btn1Click.bind(this);}// 定义实例方法btn1Click() {this.setState({counter: this.state.counter + 1,});}btn2Click = () => {this.setState({counter: this.state.counter - 1,});};btn3Click() {this.setState({counter: this.state.counter + 10,});}render() {const { counter } = this.state;return (<div><h2>{this.state.counter}</h2>{/*1. this 绑定方式一:bind绑定 */}<button onClick={this.btn1Click}>按钮1</button>{/*2. this 绑定方式二:ES6 class fields */}<button onClick={this.btn2Click}>按钮2</button>{/*3. this 绑定方式三: 直接传入箭头函数(很重要) */}<button onClick={() => console.log("---")}>按钮3</button><button onClick={() => this.btn3Click()}>按钮3</button></div>);}
}

5.5.3 事件参数传递

在执行事件函数时,有可能需要获取一些参数信息:比如event对象、其他参数

情况一:获取 event 对象

  • 很多时候我们需要拿到event对象来做一些事情(比如阻止默认行为)
  • 那么默认情况下,event 对象有被直接传入,函数就可以获取到event对象;

情况二:获取更多参数

  • 有更多参数时,我们最好的方式就是传入一个箭头函数,主动执行的事件函数,并且传入相关的其他参数;
class App extends React.Component {btnClick(event) {console.log(event); // 事件对象console.log(this); // 组件实例}btn2Click(a, b, c) {console.log(a, b, c); //为 foo 30 e事件}btn3Click(a, b, c) {console.log(a, b, c); //为 e事件 foo 30}render() {return (<div>{/* 事件传参;*/}<button onClick={this.btnClick.bind(this)}>按钮1</button><button onClick={(event) => {this.btnClick(event);}}>按钮1</button>{/* 传递额外参数*/}{/* 利用 bind传递,参数顺序复杂(不推荐)  这里 bind 'foo' 传给 a, 30 传给b,事件 event对象 传给了c*/}<button onClick={this.btn2Click.bind(this, "foo", 30)}>按钮2</button>{/* 推荐写法, 参数传递清晰*/}<button onClick={(event) => {this.btn3Click(event, "foo", 30);}}>按钮3</button></div>);}
}
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<App />);

5.6 条件渲染

某些情况下,界面的内容会根据不同的情况显示不同的内容,或者决定是否渲染某部分内容:

  • 在vue中,我们会通过指令来控制:比如 v-if、v-show;
  • 在React中,所有的条件判断都和普通的JavaScript代码一致

常见的条件渲染的方式

  • 条件判断语句 : 适合逻辑较多的情况
  • 三元运算符 : 适合逻辑比较简单的情况
  • 与运算符&& : 适合如果条件成立,渲染某一个组件;如果条件不成立,什么内容也不渲染
  • 类似vue的 v-show的效果:(主要是控制 display 属性是否为none )
class App extends React.Component {constructor() {super();this.state = {isReady: false,person: {},};}render() {const { isReady, person } = this.state;let ele = null;// 1. 条件判断方式一:使用 if elseif (isReady) {ele = <h2>准备好了</h2>;} else {ele = <h2>未准备好</h2>;}return (<div>{/* 1. 方式一:根据条件给变量赋值不同的内容*/}<div>{ele}</div>{/* 2. 方式二:三元运算符*/}<div>{isReady ? <h2>ok</h2> : <h2>no</h2>}</div>{/* 3. 方式三:&&逻辑与运算 如下表示:有 person,才显示 h2及里面的内容 */}{/* 场景:当某一个值可能为underfined/null/false时 使用&&进行判断 */}<div>{person && <h2>{person.name + "--" + person.age}</h2>}</div>{/* 方式四:实现 vue 的 v-show效果 */}<div style={{ display: isReady ? "block" : "none" }}>gogogo</div></div>);}
}

5.7 列表渲染

5.7.1 React的列表渲染

在React中并没有像 Vue 模块语法中的 v-for 指令,而且需要我们通过JavaScript代码的方式组织数据,转成 JSX:

  • 很多从Vue转型到React的开发者会不习惯,认为Vue的方式更加的简洁明了;
  • 但是React中的JSX正是因为和JavaScript无缝的衔接,让它可以更加的灵活
  • 在React中,展示列表最多的方式就是使用数组map 高阶函数;
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =><li>{number}</li>
);
// 渲染到 jsx中
<ul>{listItems}<ul>

很多时候我们在展示一个数组中的数据之前,需要先对它进行一些处理:

  • 比如过滤掉一些内容:filter函数
  • 比如截取数组中的一部分内容:slice函数

【示例】

class App extends React.Component {constructor() {super();this.state = {students: [{ id: 1, name: "foo", score: 85 },{ id: 2, name: "goo", score: 78 },{ id: 3, name: "hoo", score: 99 },],};}render() {const { students } = this.state;// 展示分数大于 80的学生const filterStudents = students.filter((item) => {return item.score > 80;});// 展示分数大于60的人,但只显示前两条;const sliceStudents = students.slice(0, 2);return (<div><h1>学生信息</h1>{/* 链式调用也很常见 */}{students.filter((item) => item.score > 60).slice(0, 2).map((item) => {return (<div key={item.id}><h2>学号:{item.id}</h2><h2>姓名:{item.name}</h2><h3>成绩:{item.score}</h3></div>);})}</div>);}
}

5.7.2 列表中的key

  • key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此应当给数组中的每一个元素赋予一个确定的标识。
  • 传入的数组中,每个元素最好带有唯一的标志,如id,实在没有才用 index
  • key主要的作用是为了提高 diff 算法时的效率;
const numbers = [{id:1,name:'foo'}, {id:2,name:'goo'}, {id:3,name:'coo'}];
const listItems = numbers.map((item) =><li key={item.id}>{item.name}</li>
);
// index (不推荐)
const listItems2 = numbers.map((item,index) =><li key={index}>{item.name}</li>
);

常见警告:告诉我们需要在列表展示的 jsx 中添加一个key

5.8 JSX的本质

5.8.1 了解JSX的语法糖

实际上,jsx 仅仅只是 React.createElement(component, config, ...children) 函数的语法糖。所有的 jsx 最终都会被转换成 React.createElement 的函数调用。

createElement需要传递三个参数:

  • 参数一:type

    • 当前 ReactElement 的类型;
    • 如果是标签元素,那么就使用字符串表示 如 “div”;
    • 如果是组件元素,那么就直接使用组件的名称;
  • 参数二:config
    • 所有 jsx 中的属性都在config中以对象的属性和值的形式存储;
    • 比如传入 className 作为元素的 class;
  • 参数三:children
    • 存放在标签中的内容,以 children 数组的方式进行存储;
    • 如果是多个元素,React内部有对它们进行处理

5.8.2 查看 Babel 对jsx的解析

  • 默认 jsx 是通过 babel 帮我们进行语法转换的,所以之前写的 jsx 代码都需要依赖 babel。
  • 在babel的官网中快速查看转换的过程:https://babeljs.io/repl/#?presets=react
//=================== 下面为 JSX语法
<div className="app"><div className="header"><h1 title="标题">我是标题</h1></div><div className="content"><button onClick={() => console.log("aaa")}>打印a</button><button onClick={() => console.log("bbb")}>打印b</button></div><div className="footer"><p>我是尾部</p></div>
</div>
// ==============下面为babel转化后的函数调用形式
/*#__PURE__*/React.createElement("div", {className: "app"
}, /*#__PURE__*/React.createElement("div", {className: "header"
}, /*#__PURE__*/React.createElement("h1", {title: "\u6807\u9898"
}, "\u6211\u662F\u6807\u9898")), /*#__PURE__*/React.createElement("div", {className: "content"
}, /*#__PURE__*/React.createElement("button", {onClick: () => console.log("aaa")
}, "\u6253\u5370a"), /*#__PURE__*/React.createElement("button", {onClick: () => console.log("bbb")
}, "\u6253\u5370b")), /*#__PURE__*/React.createElement("div", {className: "footer"
}, /*#__PURE__*/React.createElement("p", null, "\u6211\u662F\u5C3E\u90E8")));


5.8.3 编写 React.createElement

  • 把 上面 babel 编译的结果替换原来的 jsx 语法,运行照常,但这种形式不便于开发
  • 在这样的情况下,就不需要了依赖 babel 了;如:<script src="../react/babel.min.js"></script> 就可以删除掉了;
  • 同时,type="text/babel" 也可以不添加;
class App extends React.Component {constructor() {super();}render() {return /*#__PURE__*/React.createElement("div", {className: "app"}, /*#__PURE__*/React.createElement("div", {className: "header"}, /*#__PURE__*/React.createElement("h1", {title: "\u6807\u9898"}, "\u6211\u662F\u6807\u9898")), /*#__PURE__*/React.createElement("div", {className: "content"}, /*#__PURE__*/React.createElement("button", {onClick: () => console.log("aaa")}, "\u6253\u5370a"), /*#__PURE__*/React.createElement("button", {onClick: () => console.log("bbb")}, "\u6253\u5370b")), /*#__PURE__*/React.createElement("div", {className: "footer"}, /*#__PURE__*/React.createElement("p", null, "\u6211\u662F\u5C3E\u90E8")));;}
}

5.8.4 虚拟DOM的创建过程

上面通过 React.createElement 最终创建出来一个 ReactElement对象

React 为什么要创建它,这个ReactElement对象是什么作用?

  • React 要利用 ReactElement 对象组成了一个JavaScript的对象树
  • 这个 JavaScript的对象树就是虚拟DOM(Virtual DOM);

查看ReactElement的树结构,可以将之前的 jsx 返回结果保存到一个变量中,打印出来


ReactElement 最终形成的树结构就是 Virtual DOM;

5.8.5 声明式编程

  • 虚拟DOM帮助我们从命令式编程转到了声明式编程的模式
  • React官方的说法:Virtual DOM 是一种编程理念。
    • 在这个理念中,UI以一种理想化或者说虚拟化的方式保存在内存中,并且它是一个相对简单的JavaScript对象
    • 我们可以通过 root.render 让 虚拟DOM 和 真实DOM同步起来,这个过程中叫做协调(Reconciliation);
  • 这种编程的方式赋予了React声明式的API:
    • 你只需要告诉React希望让UI是什么状态;
    • React来确保DOM和这些状态是匹配的;
    • 你不需要直接进行DOM操作,就可以从手动更改DOM、属性操作、事件处理中解放出来;

6. 脚手架&创建项目

6.1 前端工程的复杂化

如果我们只是开发几个小的 demo 程序,那么永远不需要考虑一些复杂的问题:

  • 比如目录结构如何组织划分;
  • 比如如何管理文件之间的相互依赖;
  • 比如如何管理第三方模块的依赖;
  • 比如项目发布前如何压缩、打包项目等等…

现代的前端项目已经越来越复杂了:

  • 不会再是在HTML中引入几个 css 文件,引入几个编写的 js 文件或者第三方的 js 文件这么简单;
  • 比如 css 可能是使用 less、sass 等预处理器进行编写,我们需要将它们转成普通的 css 才能被浏览器解析;
  • 比如JavaScript代码不再只是编写在几个文件中,而是通过模块化的方式,被组成在成百上千个文件中,我们需要通过模块化的技术来管理它们之间的相互依赖;
  • 比如项目需要依赖很多的第三方库,如何更好的管理它们(比如管理它们的依赖、版本升级等);

为了解决上面这些问题,我们需要再去学习一些工具:

  • 比如 babel、webpack、gulp,配置它们转换规则、打包依赖、热更新等等;
  • 脚手架的出现,就是帮助我们解决这一系列问题的;

6.2 脚手架概念

编程中提到的脚手架(Scaffold),其实是一种工具,帮我们可以快速生成项目的工程化结构

  • 每个项目作出完成的效果不同,但是它们的基本工程化结构是相似的;
  • 既然相似,就没有必要每次都从零开始搭建,完全可以使用一些工具,帮助我们生产基本的工程化模板
  • 不同的项目,在这个模板的基础之上进行项目开发或者进行一些配置的简单修改即可;
  • 这样也可以间接保证项目的基本结构一致性,方便后期的维护;
    总结:脚手架让项目从搭建到开发,再到部署,整个流程变得快速和便捷

6.2.1 前端脚手架

对于现在比较流行的三大框架都有属于自己的脚手架:

  • Vue 的脚手架:@vue/cli
  • Angular 的脚手架:@angular/cli
  • React 的脚手架:create-react-app

它们的作用都是帮助我们生成一个通用的目录结构,并且已经将我们所需的工程环境配置好

使用这些脚手架需要依赖什么呢?

  • 目前这些脚手架都是使用 node 编写的,并且都是基于 webpack 的;
  • 所以我们必须在自己的电脑上安装 node 环境;

6.2.2 安装React脚手架

  • React 脚手架本身需要依赖 node,所以我们需要安装node环境:
  • node官网地址:https://nodejs.org/zh-cn/
  • 注意:这里推荐大家下载 LTS(Long-term support )版本,是长期支持版本,会比较稳定;

下载后,双击安装即可:
1.安装过程中,会自动配置环境变量;
2.安装时,会同时帮助我们安装 npm 管理工具


安装完 node 之后(node -v 检测是否成功安装) ,终端用管理员方式运行 ,输入如下命令安装 react 脚手架

npm i create-react-app --global

6.2 创建 React 项目

通过脚手架来创建 React 项目,命令如下:

create-react-app 项目名称
  • 注意:项目名称不能包含大写字母
  • 另外还有更多创建项目的方式,可以参考官网:https://github.com/facebook/create-react-app

创建完成后,进入项目根目录,运行如下命令就可以将项目跑起来:

npm run start

6.3 目录结构分析

通过 VSCode 打开项目,目录如下


下面利用 tree-node-cli 工具打印项目目录结构

├── node_modules // 存放下载的包
├── README.md // readme 说明文档
├── package-lock.json // 记录node_modules目录下所有模块(包)详细信息,并锁定安装模块的版本号
├── package.json // 对整个应用程序的描述:包括应用名称,版本号,一些依赖包、项目启动、打包等
├── public
│   ├── favicon.ico // 应用程序顶部的icon图标
│   ├── index.html // 应用的index.html入口文件
│   ├── logo192.png // 在被 manifest.json中使用
│   ├── logo512.png // 在被 manifest.json中使用
│   ├── manifest.json // 和 Web app 配置相关
│   └── robots.txt // 指定搜索引擎可以或不能爬取哪些文件
└── src├── App.css // App组件相关的样式├── App.js // App组件相关的代码文件├── App.test.js // App组件的测试代码文件├── index.css // 全局的样式文件├── index.js // 整个应用程序的入口文件├── logo.svg // 启动项目是,页面上看到的React图标├── reportWebVitals.js // 默认写好的注册PWA相关的代码└── setupTests.js // 测试初始化文件

6.4 PWA(了解)

上面整个目录结构对于学过 VUE 的其实很好理解,只是有一个PWA相关的概念:

  • PWA全称Progressive Web App,即渐进式WEB应用;
  • 一个 PWA 应用首先是一个网页, 可以通过 Web 技术编写出一个网页应用;
  • 随后添加上 App Manifest 和 Service Worker 来实现 PWA 的安装和离线等功能;
  • 这种Web存在的形式,我们也称之为是 Web App;

PWA解决了哪些问题呢?

  • 可以添加至主屏幕,点击主屏幕图标可以实现启动动画以及隐藏地址栏;
  • 实现离线缓存功能,即使用户手机没有网络,依然可以使用一些离线功能;
  • 实现了消息推送
  • 等等一系列类似于 Native App 相关的功能;

更多PWA相关的知识,可以查看 MDN文档

6.5 脚手架中的webpack

React脚手架默认是基于Webpack来开发的;

但是,为什么我们没有在目录结构中看到任何 webpack 相关的内容

  • 原因是 React 脚手架将 webpack 相关的配置隐藏起来了(其实从Vue CLI3开始,也是进行了隐藏);

如果我们希望看到 webpack 的配置信息,应该怎么来做呢?

  • 我们可以执行一个 package.json 文件中的一个脚本:"eject": "react-scripts eject";运行下面代码
npm run eject

这个操作是不可逆的,所以在执行过程中会给与我们提示;

执行命令之后会多出如下两个文件 configscripts ;即将原本 creat react appwebpackbabel 等相关配置的封装弹射出了

6.6 从零编写结构

6.6.1 删除文件结构

为了梳理各个文件的作用,这里先删掉某些自动创建的文件;再根据需要(如报错提示等),自己逐一创建:

  • 1.将 src 下的所有文件都删除
  • 2.将 public 文件下除了 favicon.icoindex.html 之外的文件都删除掉


此时终端肯定会报错,如 找不到 src 下的 index.js 文件

Module not found: Error: Can't resolve '你的项目根目录\src\index.js' in '你的项目根目录'

下面将解决报错并编辑基本代码把项目跑起来

6.6.2 编写代码

在 src 目录下,创建一个 index.js 文件,因为这是 webpack 打包的入口。

index.js 中开始编写 React 代码:

  • 在模块化开发中,我们需要手动的来导入React、ReactDOM,因为它们都是在我们安装的模块中;
// index.js 文件
import ReactDom from "react-dom/client";
// 这里把root.render 中的内容单独提出来作为一个组件 App.jsx
import App from "./App.jsx";
// 创建并渲染根
const root = ReactDom.createRoot(document.querySelector("#root"));
root.render(<App></App>);

如果不希望直接在 root.render 中编写过多的组件代码,可以单独抽取一个模块(组件)中 App.jsApp.jsx

// App.js 或 App.jsx
import React from "react";
class App extends React.Component {render() {return <div>hello react</div>;}
}
export default App;

至此,项目能正常运行,界面显示 hello react

React学习笔记一(React入门+JSX+脚手架)相关推荐

  1. React学习笔记-2-什么是jsx?如何使用jsx?

    什么是jsx?     JSX是JavaScript  XML 这两个单词的缩写,xml和html非常类似,简单来说可以把它理解成使用各种各样的标签,大家可以自行 百度.所以jsx就是在javascr ...

  2. React学习笔记六 React拓展 - SetState

    React拓展 - setState setState更新状态的两种写法 1.setState({}, [callback]) export default class Test extends Co ...

  3. react学习笔记1--基础知识

    什么是react A JAVASCRIPT LIBRARY FOR BUILDING USER INTERFACES[React是一个用于构建用户界面的JavaScript库.] React之所以快, ...

  4. React学习笔记 - 组件Props

    React Learn Note 4 React学习笔记(四) 标签(空格分隔): React JavaScript 三.组件&Props 组件可以将UI切分成一些独立的.可复用的部件,这样你 ...

  5. React学习笔记:入门案例

    React学习笔记:入门案例 React 起源于 Facebook 内部项目,因为市场上所有 JavaScript MVC 框架都不令人满意,公司就决定自己写一套,用来架设 Instagram 的网站 ...

  6. react render没更新_web前端教程分享React学习笔记(一)

    web前端教程分享React学习笔记(一),React的起源和发展:React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写 ...

  7. react组件卸载调用的方法_好程序员web前端培训分享React学习笔记(三)

    好程序员web前端培训分享React学习笔记(三),组件的生命周期 React中组件也有生命周期,也就是说也有很多钩子函数供我们使用, 组件的生命周期,我们会分为四个阶段,初始化.运行中.销毁.错误处 ...

  8. React学习笔记(五) 状态提升

    状态提升究竟是什么东西呢?别急,下面让我们一步一步来看看究竟要怎么使用状态提升 假设我们有这样一个需求,提供两个输入框(分别属于两个组件),保证输入框里面的内容同步 好,下面我们先来封装一个输入框组件 ...

  9. Hadoop学习笔记(1) ——菜鸟入门

     Hadoop学习笔记(1) --菜鸟入门 Hadoop是什么?先问一下百度吧: [百度百科]一个分布式系统基础架构,由Apache基金会所开发.用户能够在不了解分布式底层细节的情况下.开发分布式 ...

最新文章

  1. 古怪的ConfigurationManager类
  2. 关于Python类属性与实例属性的讨论
  3. 学习开发自己的composer包,并使用GitHub实时更新到Packagist
  4. SQL validation failed.Column ‘content‘ not found in any table
  5. 【Spring】Spring lazy-init:bean延迟初始化
  6. 计算机 360云盘删除,xp系统下如何删除360云盘显示图标
  7. pyqt5 显示更新进度条_python3.x+pyqt5实现主窗口状态栏里(嵌入)显示进度条功能...
  8. 基于二进制粒子群算法的配电网故障诊断- 附代码
  9. 电路交换、报文交换、分组交换的区别与优缺点
  10. 如何将CHM转换为PDF文件?
  11. 从java代码到网络编程
  12. 什么是android应用程序未安装,Android 解决应用程序未安装的三种方法
  13. 苹果手机如何只用数据线修改定位
  14. 怎么制作书单视频?免费制作书单视频软件分享
  15. 【总结-学习-提升】web应用为什么需要tomcat容器
  16. PMP项目管理备考资料都有哪些?
  17. C++ 标准库之typeid
  18. 数据分析入门必知--数据分析流程
  19. [转载]软件测试从零开始
  20. opencv3.4.1: ippicv_2017u3_lnx_intel64_20170822.tgz下载包

热门文章

  1. 电动助力转向系统发展趋势
  2. NFS、FTP、SAMBA服务搭建
  3. 基于概率分析的智能AI扫雷程序秒破雷界世界纪录
  4. WPF:自定义DataGrid,双向绑定,禁用行,行索引
  5. idea 配置项目服务器地址,idea服务器配置页面在哪里
  6. @JsonFormat注解未设置时区导致的返回时间错误
  7. 学计算机的去做传感器,传感器技术
  8. 【JavaSE学习笔记】
  9. 记录字节跳动前端面试,四轮技术面
  10. python image.open 参数作用_Python图形图像处理库的介绍之Image模块 - Django's blog