简介
React是一个用于构建用户界面的JavaScript库
用户界面(User Interface,简称UI)是系统和用户之间进行交互和信息交换的介质,在这里指代的就是网页前端界面
传统的JS的问题
- 需要频繁的操作DOM,而操作DOM对于不同的浏览器之间存在一定的兼容性问题
- 而过多的兼容性代码,会在一定程度上造成业务逻辑代码的冗余
- 代码组织和规范的问题
- 我们编写页面的时候,需要过多的关注界面的细节,也就是说我们即需要操作DOM,还需要去管理和维护页面的状态(数据)
- 数据(状态)的定义和使用往往会分散到各个地方,不方便管理和维护
react框架的思想
- 以组件的方式去划分一个个功能模块
- 组件内以jsx来描述UI的样子,以state来存储组件内的状态
- 当应用的状态发生改变时,通过setState来修改状态,状态发生变化时,UI会自动发生更新
特点
声明式编程
声明式编程只需要维护自己的状态,当状态改变时,React可以根据最新的状态去渲染我们的UI界面
在React中,表示界面的JSX是依赖于state的,
而当state发生改变的时候,可以使用setState去重新调用render函数,从而重新渲染界面
在react中,上述的f函数就是render函数
组件化开发
网站的整体看成是一个组件,也就是根组件
随后网页根据功能点进行划分成一个个小的组件
最后再通过打包工具,将这一个个小的组件整合起来
跨平台开发
- 2013年,React发布之初主要是开发Web页面
- 2015年,Facebook推出了ReactNative,用于开发移动端跨平台
- 2017年,Facebook推出ReactVR,用于开发虚拟现实Web应用程序
React依赖的库
库 | 说明 |
---|---|
react | 包含了react和react-native所共同拥有的核心代码 |
react-dom | react渲染在不同平台所需要的核心代码 |
babel | 编译器 |
react-dom会根据需要渲染的平台,将对于的vdom渲染成对应的dom或组件
平台 | 说明 |
---|---|
web端 | react-dom会讲jsx最终渲染成真实的DOM,显示在浏览器中 |
native端 | react-dom会讲jsx最终渲染成原生的控件(比如Android中的Button,iOS中的UIButton) |
babel是目前前端使用非常广泛的编辑器,
虽然ES6很方便,但是某些老版本浏览器不支持ES6,只支持ES5
babel让我们可以直接在编写代码的时候使用ES6
在部署的时候,会将我们ES6的代码转换为ES5的代码,从而适配我们所有需要适配的浏览器
默认情况下开发React其实可以不使用babel
但是React默认情况下需要使用React.createElement来编写页面结构,
而React.createElement本身编写是非常繁琐的和可读性差
所以React提供了React.createElement的语法糖JSX
默认情况下jsx(JavaScript XML),React是无法直接解析的
所以需要babel将jsx转换为React.createElement形式的代码后在交给React来进行解析
Hello World
<!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>
<!-- 全局挂载React对象 -->
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<!-- 全局挂载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>
</head>
<body>
<!-- 挂载点 -->
<div id="app"></div>
<!-- type="text/babel" -> 告诉浏览器这些脚本要交给babel进行解析 -->
<script type="text/babel">
// 将参数1的内容挂载到参数2上,并覆盖原本挂载点中的内容
// 参数1 -- JSX对象 需要渲染的内容 经过React.createElement处理后返回ReactRenderObject对象 也就是VDom对象
// 参数2 --- 挂载点
ReactDOM.render(<h2>Hello World</h2>, document.getElementById('app'))
</script>
</body>
</html>
命令式编程 vs 声明式编程
示例
- 在界面显示一个文本: Hello World
- 点击下方的一个按钮,点击后文本改变为Hello React
命令式编程
<!--
这种即需要关心页面状态又需要关心模板
还需要在界面发生改变的时候,手动去操作DOM来更新界面的编程范式就被称之为响应式编程
响应式编程的本质就是每一步都是向浏览器发送一条条的指令,凡事都是亲力亲为的
-->
<div id="app">
<h2 id="msg"></h2>
<button id="btn">change</button>
</div>
<script type="text/babel">
let msg = 'Hello World'
const msgEl = document.getElementById('msg')
const btnEl = document.getElementById('btn')
msgEl.innerHTML = msg
btnEl.addEventListener('click', () => {
msg = 'Hello React'
msgEl.innerHTML = msg
})
</script>
声明式编程
let msg = 'Hello World'
function render() {
// 使用小括号是为了表示成一个整体,同时可以进行换行显示
const jsx = (
// JSX有且只能有一个根元素
<div>
{/* 使用大括号语法来插入变量 */}
<h2>{msg}</h2>
{/* 在react中原生事件使用小驼峰,因为react对原生事件进行了二次封装 */}
<button onClick={ handleChange }>change</button>
</div>
)
ReactDOM.render(jsx, document.getElementById('app'))
}
function handleChange() {
msg = 'Hello React'
render()
}
// 初始化渲染
render()
但是上边的代码看起来有点凌乱,且整个逻辑其实可以看做一个整体,也就是整个逻辑本身就可以看成是一个功能点,即整个逻辑其实可以封装为一个组件,也就是根组件
// 这种只要定义模板和状态即可,不需要我们频繁的去操作DOM的编程范式就是响应式编程
// 一个类继承自React.Component后就成为了React组件
class App extends React.Component {
constructor() {
// 继承类必须在构造器的第一行调用super方法
// 以初始化父类实例
super()
// 如果定义的对象是需要响应式的对象
// 也就是那些当值发生改变后,界面也需要相应发生刷新的那些对象
// 在定义这些响应式对象的时候,必须定义在state对象中
this.state = {
msg: 'Hello World'
}
}
// 组件必须实现一个render函数
// 该函数需要返回对应的jsx对象
// 用于告诉React需要显示的template
render() {
// 如果return后边的div需要和return分两行来进行编写
// 就需要给div外包裹小括号,以表示这是个整体
return (
<div>
{/* render不是react中的原生函数,所以其内部的this指向是正确的 */}
<h2>{this.state.msg}</h2>
<button onClick={ this.handleChange }>change</button>
</div>
)
}
// 默认情况下,React内置的原生事件在调用的时候,会将原生事件中的this置为undefined
// 所以我们需要使用箭头函数进行重置
handleChange = () => {
// render函数本质是返回JSX对象
// 但是单独调用render函数并不会将新的JSX交给ReactDOM来重新渲染
// 所以在更新响应式数据的时候,必须调用setState函数
// 在实际开发中,我们一般不会手动去调用render函数
// this.render()
// 响应式数据发生改变的时候
// 调用setState方法进行界面刷新
this.setState({
msg: 'Hello React'
})
}
}
ReactDOM.render(<App />, document.getElementById('app'))
简单案例
电影列表
class App extends React.Component {
constructor() {
super()
this.state = {
movies: ['电影1', '电影2', '电影3', '电影4', '电影5']
}
}
render() {
return (
<div>
{/*
react直接显示数组的时候,会主动调用数组的join方法,分隔符为空字符串
所以这里实际展示的是this.state.moives.join('')
*/}
{ this.state.movies }
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
既然react在显示的时候,会主动调用数组的join方法,那么我们所需要做的就是为数组中的每一项包裹li标签即可
class App extends React.Component {
constructor() {
super()
this.state = {
movies: ['电影1', '电影2', '电影3', '电影4', '电影5']
}
}
render() {
return (
<ul>
{
this.state.movies.map(moive => <li>{ moive }</li>)
}
</ul>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
计数器
class App extends React.Component {
constructor() {
super()
this.state = {
count: 0
}
}
render() {
return (
<div>
<h2>{ this.state.count }</h2>
<button onClick={() => this.change(1)}>+1</button>
<button onClick={() => this.change(-1)}>-1</button>
</div>
)
}
change = step => {
this.setState({
count: this.state.count + step
})
}
}
ReactDOM.render(<App />, document.getElementById('app'))