React 简介

本章视频讲解在这里,连续看6个视频


教程说明

React(也叫Reactjs)是 Web前端开发的框架 ,大家必须先掌握Web前端开发的基础知识 HTML/CSS/JS编程,才能学习 React


而且我们这里对 React的讲解,有时会用到前面我们锻炼的项目实战

所以,大家也要先锻炼过我们提供药管系统项目实战,至少先锻炼到 09 删除客户 这里

不用框架就不行吗?

前面,我们的 药管系统项目实战 已经开发了一些功能

并没有使用 react 这样的框架,

需要动态的添加、改变界面时, 我们使用 HTML字符串模板,比如

// 列出条目时,条目里面的html内容
function genOneItemInnerHtml(item){
  return `              
  <div class='result-list-item-info'>
  <p><span class='field-name'>客户名</span><span>${item.name}</span></p>
  <p><span class='field-name'>地址</span><span>${item.address}</span></p>
  <p><span class='field-name'>电话</span><span>${item.phonenumber}</span></p>
  </div>
  <div class='result-list-item-btn-bar'>
    <span class="btn-no-border btn-delete-item">删除</span>
    <span class="btn-no-border btn-modify-item">修改</span>
  </div>
`
} 

然后通过这样使用模板字符串,实现动态的改变界面

let pEle = `        
  <div class='result-list-item' id='${item.id}'>
    ${genOneItemInnerHtml(item)}
  </div>
`
document.querySelector('.result-list').insertAdjacentHTML("beforeend", pEle)

我们仔细回顾思考一下,可以发现一些问题:

字符串模板如果内容比较多,没有IDE助手功能,语法不高亮,非常不方便,很容易写错。

为了实现一些操作,需要额外添加元素或者属性,把应用数据保存在里面

比如,我们点击列出客户的条目右边的删除按钮时,为了代码能知道这个条目对应的客户id, 就额外添加了一个条目div的属性id,值就是客户id

这样使得界面html变得复杂化

有些功能只需要更新界面某个局部,但是往往更新了更大的范围

这样往往会带来性能问题。



上述这些问题,一些前端框架,比如 React、Vue 等,可以比较好的解决

我们下面的教程就介绍 React框架如何使用在我们的实战项目中

react简易开发环境搭建

要集成React框架库,典型做法 涉及到编译、打包等一系列操作,后面课程会介绍

这里先不搞的太麻烦,是为了让大家减少干扰, 先搞懂核心概念。


这里先介绍一个简易的、方便演示功能的,开发环境搭建方式

就是直接在HTML文件的 <head> 中包含react相关的库,如下所示

  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" ></script>
  <script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js"></script> 
  <script src="https://unpkg.com/@babel/standalone@7.28.4/babel.min.js"></script>

注意:上面包含的库文件是开发过程中使用的 development 版本的

开发阶段应该使用开发版本,有更详细的错误提示,便于我们调试时发现问题


将来在正式发布的生产环境直接包含React库文件,应该使用 production 版本的,如下

  <script src="https://unpkg.com/react/umd/react.production.min.js" ></script>
  <script src="https://unpkg.com/react-dom/umd/react-dom.production.min.js"></script>

注意:看视频时,看到的上面库链接,使用的是公共cdn加速库,

如果将来该库地址不可用,我们网站会保持更新,确保代码使用的是 可用的cdn加速库

实际上,我们建议生产环境不要使用公共cdn加速库,因为可靠性不能保证

我们需要搭建自己的cdn环境,实战班会有这方面的锻炼

JSX

简介

看下面的一个例子

<head>
 
  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" ></script>
  <script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js"></script>  
  <script src="https://unpkg.com/@babel/standalone@7.28.4/babel.min.js"></script>
  
</head>

<body>
    
<div id="root"></div>

<script type="text/babel">
let dt = new Date(); 
let element = <div>
  <p>当前日期时间是</p>
  <span>{dt.toLocaleDateString()}</span> --
  <span>{dt.toLocaleTimeString()}</span>
</div>
    
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
</script>
  
</body>

大家注意到 element变量 赋值右边的表达式

它居然好像直接就是一个html的代码片段, 但是却没有放到引号中,作为字符串!!!

和html的明显区别是,里面包含像下面这样的 js表达式

{dt.toLocaleDateString()}

js表达式放在花括号中。


这种 类似HTML的标记语法 被称之为 JSX , 全称 JavaScript XML

这种语法是React引入的。

虽然React并不是必须使用JSX,但是它是我们使用React的重要原因之一。

它解决了我们前面说的,直接使用html片段字符串 的问题。

vscode 这样的IDE都支持它,语法高亮,自动补齐、错误提示 这些功能提升了写模板html的效率

而且,更重要的是, 支持内嵌 js 表达式, 使得HTML模板功能非常灵活直观。


上例中,包含了jsx内容的代码,是放在这样的标签中的

<script type="text/babel">

引入的 babel内置了jsx代码编译器, 看到 type="text/babel" 这样的js代码段,

会把JSX语句转译为浏览器能理解的js代码


比如,上例中,最终会被babel转化为类似这样的js代码

let dt = new Date();

let element = React.createElement(
  "div", null, 
  React.createElement("p", null, "当前日期时间是"), 
  React.createElement("span", null, dt.toLocaleDateString()), 
  " --", 
  React.createElement("span", null, dt.toLocaleTimeString())
);

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

所以,一定要切记:JSX只是看起来像HTML,它本质上是 js表达式


可以在线转化 https://babeljs.io/repl/


可以发现,最终 JSX 元素 对应的是 React.createElement 方法的返回值

它是 React element 类型的对象,内部封装了将来要产生的的HTML元素信息

React.createElement 方法的详细描述,可以点击这里,查看文档


JSX 元素 , 也就是 React.createElement 返回值, 本质就是一个 js 的Object。

它 代表了 一个 DOM element 或者 user-defined component.

It's not the actual DOM element itself, but rather a description of what the DOM element should look like. This object contains the following properties:

type: The type of the element, which can be a tag name string (e.g., 'div', 'span'), a React component type (a class or a function), or a React fragment type.

props: An object containing the properties (attributes) that will be passed to the element or component.

key: An optional key used by React to identify elements during updates.

ref: An optional ref used to access the underlying DOM element or component instance.

React uses these React elements to build and update the virtual DOM, which is an in-memory representation of the actual DOM. When the state or props of a component change, React compares the new virtual DOM with the previous one and updates only the necessary parts of the actual DOM.


后面的代码

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element)

先通过 ReactDOM.createRoot 创建了一个 React root 对象

root对象的render方法,就是根据 React element 对象里面的信息,转化为对应的实际界面HTML,呈现在界面上。

这个 render ,翻译为中文叫 渲染

这里的 root 变量 对应的 ,就是要呈现在哪个HTML元素里面


注意:React支持创建多个root对象,比如

const root1 = ReactDOM.createRoot(document.getElementById('root1'));
root1.render(element1)

const root2 = ReactDOM.createRoot(document.getElementById('root2'));
root2.render(element2)

元素属性表达式

JSX 中,元素属性值 如果是 js 表达式,不需要加引号

如下

const element = <img src={user.avatarUrl}></img>



如果属性设置值为js表达式,并且里面还有多个子属性,比如style,需要这样设置

let fontSize = '.92em'
const element = <div 
  style={{
    color:'green',
    border:"1px #3c763d solid", 
    marginTop:'1em',
    fontSize: fontSize
  }}>
  </div>

这是我初学JSX最让我困惑的地方。

为啥要这样呢?

因为JSX花括号里面的一定是 符合js语法的表达式 ,所以用一个对象来表示键值对这种多个属性的设置

所以,里面多了一对花括号


margin-top 为何要变成 marginTop 这种驼峰命名呢?

如果是因为中间的减号,可以用双引号把key括起来啊。

官方文档的说法是 : 因为 JSX本质上是js 而不是HTML,所以这样命名。

好吧,就这么理解吧,虽然感觉不是很有逻辑。

好在vscode这样的集成开发环境对JSX支持很好,名称补齐功能一样有效

空元素

如果是空元素,一定要有反斜杠表示关闭元素

比如

const element = <img src={user.avatarUrl} />;

可以用括号

比如

let element = (
  <div>
    <p>当前日期时间是</p>
    <span>{dt.toLocaleDateString()}</span> --
    <span>{dt.toLocaleTimeString()}</span>
  </div>
)

主要用于多行jsx,否则第一个元素必须写在等号那行后面

必须有顶层元素

不能出现这样的写法

let element = (  
    <p>当前日期时间是</p>
    <span>{dt.toLocaleDateString()}</span> --
    <span>{dt.toLocaleTimeString()}</span>
  
)

必须要有一个顶层元素,如果不想用 div,可以这样用一个空标签

let element = (
  <>  
    <p>当前日期时间是</p>
    <span>{dt.toLocaleDateString()}</span> --
    <span>{dt.toLocaleTimeString()}</span>
  </>
)

更新渲染内容

根据目前学到的内容,不难推测,再次调用 root 的 render 方法,可以重新渲染

比如,下面的代码就每隔1秒更新

<head> 
  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" ></script>
  <script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js"></script> 
  <script src="https://unpkg.com/@babel/standalone@7.28.4/babel.min.js"></script>  
</head>

<body>
    
<div id="root"></div>

<script type="text/babel">

function tick(){
  let dt = new Date(); 
  return (
    <div>
      <p>当前日期时间是</p>
      <span>{dt.toLocaleDateString()}</span> --
      <span>{dt.toLocaleTimeString()}</span>
    </div>
  )
}
    
const root = ReactDOM.createRoot(document.getElementById('root'))

setInterval(()=>{root.render(tick())}, 1000);

</script>
  
</body>

只更新变化的部分

上面的示例网页,如果我们打开devTools,查看元素的更新,我们可以发现 :

虽然我们render的是整个 tick 返回的div

但是每隔1秒,变化的只是这个span元素

<span>{dt.toLocaleTimeString()}</span>

因为 React 会 比较 原来的内容和更新部分, 只更新变化的部分,没有变动的不更新


如果网页应用更新部分包含大量信息的信息,而只变动了一小部分,这样可以大大的提高性能

独立 jsx 文件

当然,像上面这样HTML中包含整个代码,就没法利用集成开发环境,比如vscode的JSX代码助手的全部功能,因为文件扩展名是 html

所以,可以把jsx部分代码提取出来,单独放到一个扩展名为 jsx 的文件中、

如下

HTML文件内容

<head> 
  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" ></script>
  <script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js"></script> 
  <script src="https://unpkg.com/@babel/standalone@7.28.4/babel.min.js"></script>  
</head>

<body>
    
<div id="root"></div>

<script type="text/babel" src="logic.jsx"></script>
  
</body>

在创建一个jsx文件,文件名为: logic.jsx ,和html文件放置在同一个目录下

function tick(){
  let dt = new Date(); 
  return (
    <div>
      <p>当前日期时间是</p>
      <span>{dt.toLocaleDateString()}</span> --
      <span>{dt.toLocaleTimeString()}</span>
    </div>
  )
}
    
const root = ReactDOM.createRoot(document.getElementById('root'))

setInterval(()=>{root.render(tick())}, 1000);

操作演示一下:加上事件处理onxxxx,会自动代码提示补齐


当然,目前的做法:导入Babel,运行时转化jsx为js,效率太低了。

正式产品运行, 不会像这样,而是运行前先使用工具转化好,涉及到编译、打包等一系列操作,后面课程会介绍

这里先不搞的太麻烦,是为了让大家减少干扰, 先搞懂核心概念。