小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
React是用于构建用户界面的 JavaScript 库,也就是说React是一个核心功能是用来渲染界面UI的库
在传统使用原生开发或JQuery开发的模式下,我们需要频繁的去操作界面的细节,
也就是需要频繁的在处理业务逻辑的同时,去使用DOM来更新我们的界面
此时我们需要掌握和使用大量DOM的API,当然我们可以通过jQuery来简化和适配一些API的使用
但是DOM的操作其实是存在兼容性问题的,因为我们为了更好的兼容性,所以需要添加大量的兼容性代码
有的时候就会导致代码组织和规范的问题以及过多兼容性代码的冗余问题
同时对于原生的开发模式而言,数据(状态),往往会分散到各个地方,不方便管理和维护
所以React就使用了一种全新的模式去开发我们的UI界面
- 以组件的方式去划分一个个功能模块
- 组件内以jsx来描述UI的样子,以state来存储组件内的状态
- 当应用的状态发生改变时,通过setState来修改状态,状态发生变化时,UI会自动发生更新
特点
声明式编程
声明式编程是目前整个大前端开发的模式:Vue、React、Flutter、SwiftUI;
它允许我们只需要维护自己的状态,当状态改变时,React可以根据最新的状态去渲染我们的UI界面
组件化开发
组件化开发页面目前前端的流行趋势,我们会将杂的界面拆分成一个个小的组件
在通过构建工具将一个个小的组件在整合成我们需要的应用
多平台适配
2013年,FaceBook发布React之初主要是开发Web页面
2015年,Facebook推出了ReactNative,用于开发移动端跨平台
2017年,Facebook推出ReactVR,用于开发虚拟现实Web应用程序
Hello React
需求背景:
- 在界面显示一个文本:Hello World
- 点击下方的一个按钮,点击后文本改变为Hello React
React开发依赖
和Vue不同的时候,进行React开发的时候,我们需要使用三个依赖,且他们之间引入存在着先后关系
-
react: 包含react所必须的核心代码 --- 生成VDOM
-
react-dom: react 渲染在不同平台所需要的核心代码 --- 根据不同的平台将VDOM渲染成不同的控件
- web端: react-dom会讲jsx最终渲染成真实的DOM,显示在浏览器中
- native端: react-dom会讲jsx最终渲染成原生的控件(比如Android中的Button,iOS中的UIButton)。
-
babel: 将jsx转换成React代码的工具
- Babel ,又名 Babel.js,是目前前端使用非常广泛的编辑器、转移器
- 比如当下很多浏览器并不支持ES6的语法,但是确实ES6的语法非常的简洁和方便,我们开发时希望使用它
- 那么编写源码时我们就可以使用ES6来编写,之后通过Babel工具,将ES6转成大多数浏览器都支持的ES5的语法。
- 默认情况下开发React其实可以不使用babel
- 但是前提是我们自己使用 React.createElement 来编写源代码,它编写的代码非常的繁琐和可读性差
- 那么我们就可以直接编写jsx(JavaScript XML)的语法,并且让babel帮助我们转换成React.createElement
- 也就是说JSX是React.createElement 这种书写React代码的语法糖
- 需要通过babel的转换将JSX转换后,React代码才可以识别对应的代码,否则React无法进行正常的解析
<!--
crossorigin的属性,这个属性的目的是为了拿到跨域脚本的错误信息
如果没有这个属性,远程脚本发生错误的时候,只会输出script error,不会输出具体的信息
-->
<!-- react.js 会在全局挂载React对象 -->
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<!-- react-dom.js 会在全局挂载ReactDOM对象 -->
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
声明式 vs 命名式
命名式
传统的开发模式,所有的每一步操作都需要我们给浏览器一步步的指令, 也就是所有的操作都需要我们亲力亲为
这也就就导致了我们需要手动进行数据和界面的存取,更新和兼容性处理等操作
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="content"></div>
<button id="btn">change content</button>
<script>
const dvElem = document.getElementById('content')
const btnElem = document.getElementById('btn')
dvElem.innerHTML = 'Hello World'
btnElem.addEventListener('click', () => dvElem.innerHTML = 'Hello React')
</script>
</body>
</html>
声明式
声明式编程只需要在我们按照预先定义好的规则,在对应位置对数据和操作进行声明(定义)即可 而界面的具体渲染和更新不需要我们执行,由对应使用的框架执行即可
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<!--
挂载点,react会将生成后的内容渲染在挂载点下,
如果挂载点下原本存在内容会被覆盖
-->
<div id="app"></div>
<!--
需要在script上添加属性 -- type="text/babel"
以表示这个script中的内容在交给浏览器进行解析之前
需要先交给babel进行解析
-->
<script type="text/babel">
let message = 'Hello World'
function render() {
// 参数1,需要渲染的内容(JSX)
// --- 有且只能有一个根元素
// --- 如果需要换行,需要在最外边加上小括号,以表示他们是一个整体
// --- JSX中只有多行注释,书写格式为{/* */}
ReactDOM.render((
<div>
{/*
在JSX中使用大括号语法来在模板中使用变量和js表达式
*/}
<h2>{message}</h2>
{/*
react中的事件使用的是小驼峰,因为React对原生dom事件进行了二次封装
以便于在实际渲染的时候,可以将其转换为浏览器事件对象或APP中的控件所对应的事件对象
*/}
<button onClick={change}>change content</button>
</div>
), document.getElementById('app'))
}
function change() {
message = 'Hello React'
// 和vue不同的是,在react中如果状态发生了改变
// 需要手动显示的去通知react重新调用render方法来使用新的数据重新渲染界面
render()
}
// 初始渲染界面
render()
</script>
</body>
</html>
使用React的类组件对上述的案例进行重构
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
/*
在React中一个类满足以下两个条件就可以被称之为组件
1. 继承自React.Component或React.pureComponent
2. render方法是唯一一个必须需要实现的方法,render方法返回值为需要渲染的jsx对象
*/
class App extends React.Component {
constructor() {
super()
// 所有需要在界面中使用,且当状态发生改变的时候,需要重新渲染界面的数据
// 都必须被定义在state这个对象中
this.state = {
message: 'Hello World'
}
}
render() {
return (
<div>
<h2>{ this.state.message }</h2>
{/*
在react中对原生DOM事件进行封装的时候
因为所有的组件都可以执行对应的DOM事件
所以在React内部调用原生的DOM事件的时候使用的是 类似于onClick.call(undefined)的方式进行调用
也就是内部的this是undefined,所以这里使用箭头函数来修正this
因为在构造器和render函数内部可以获取正确的this
*/}
<button onClick={() => { this.change() }}>change content</button>
</div>
)
}
change() {
// 所有需要在状态发生改变的时候重新刷新界面进行重新渲染的数据
// 在更新的时候都需要通过setState方法进行调用
// 内部会使用Object.assign方法对state中的数据进行更新后
// 在通知react重新调用render方法来重新渲染界面
this.setState({
message: 'Hello React'
})
}
}
<!--
组件本质上就是拥有html,css,js的自包含块
可以认为组件是有样式和交互的自定义html元素标签
-->
ReactDOM.render(<App />, document.getElementById('app'))
</script>
</body>
</html>
阶段案例
电影列表
class App extends React.Component {
constructor() {
super()
this.state = {
movies: ['肖申克的救赎', '阿甘正传', '泰坦尼克号', '盗梦空间']
}
}
render() {
return (
<ul>
{/*
JSX会自动遍历数组的每一项进行显示
*/}
{
this.state.movies.map(moive => <li key={moive}>{ moive }</li>)
}
</ul>
)
}
}
计数器案例
class App extends React.Component {
constructor() {
super()
this.state = {
count: 0
}
}
render() {
return (
<div>
<div>count: { this.state.count }</div>
<button onClick={() => { this.changeCount(1) }}>increment</button>
<button onClick={() => { this.changeCount(-1) }}>decrement</button>
</div>
)
}
changeCount(v) {
this.setState({
count: this.state.count + v
})
}
}