我正在参加「掘金·启航计划」
React开发依赖
react:包含react所必须的核心代码
react-dom:react渲染在不同平台所需要的核心代码
babel:将jsx转换成React代码的工具
三个库各司其职,在React0.14之前是没有react-dom这个概念的,所以所有核心代码都包含在react中
目前之所以拆分成react与react-dom是由于react native:
react中包含了react web与react native所共同拥有的核心代码
react dom针对web和native所完成的事情不同
- web端:react dom会将jsx最终渲染成真实的dom,显示在浏览器中
- native端:react dom会将jsx最终渲染成原生的控件(如Android的Button、IOS的UI Button)
React与Babel的关系
Babel是目前前端使用非常广泛的编译器
- 比如很多浏览器不支持ES6语法,我们在编写源码时使用ES6编写后可以通过Babel将ES6转换成大多浏览器支持的ES5语法
默认情况下开发React项目时可以不使用Babel,但是如果使用React的createElement来编写源码非常复杂而且可读性很差,所以我们可以直接通过编写JSX语法,并让Babel转换成React.createElement
第一个React程序
在开发前,需要引入react、react-dom、babel这三个依赖,这里暂时采用CDN引入
除了可以采用CDN引入外,还可以下载到本地进行引入,或通过npm安装依赖
<!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>Hello React</title>
</head>
<body>
<div id="root"></div>
<div id="app"></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>
<!-- babel -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</script>
</body>
</html>
引入依赖后便可以在script中编写React代码了
- 这里需要注意的是,由于我们想编写JSX语法,然后通过Babel去编译,所以需要在script标签上添加"type='text/babel'"来告知babel需要进行编译
<!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="root"></div>
<div id="app"></div>
<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>
<!-- babel -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<h2>Hello World</h2>)
const app = ReactDOM.createRoot(document.querySelector('#app'))
app.render(<h2>Hello React</h2>)
</script>
</body>
</html>
在React18之后,是可以创建多个React根的:
通过ReactDOM.createRoot(dom)来创建React根,之后渲染的内容会包含在这个根中,然后再通过创建的React根去调用render函数(传入要渲染的组件)去渲染
在React18之前是通过ReactDOM.render(根组件,根节点)来渲染页面:
ReactDOM.render(<h2>Hello World</h2>, document.querySelector("#root"))
案例一:点击按钮修改文本案例
实现方式一
如果我们想要实现点击按钮来修改页面中展示的文本,也就是点击按钮后让页面重新渲染,那么可以分以下几个步骤
- 将文本内容放到变量中保存
- 监听按钮点击时间,点击时修改文本
- 修改文本后重新执行root.render方法,让页面重新渲染
按照以上的思路,可以将root.render方法提取出来,然后在页面初次渲染与点击按钮修改文本后进行调用即可
<!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="root"></div>
<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">
const root = ReactDOM.createRoot(document.querySelector('#root'))
let message = 'Hello World'
function handleClick() {
message = 'Hello React'
// 重新render
renderFn()
}
function renderFn() {
root.render((
<div>
<h2>{message}</h2>
<button onClick={handleClick}>点击修改</button>
</div>
))
}
renderFn()
</script>
</body>
</html>
- 在JSX中,使用
'{}'
来包裹变量 - 点击事件的通过
onClick={事件名}
来绑定 - 如果render中包裹的JSX是嵌套的HTML结构时,我们可以使用
'()'
来包裹表示这是一个整体
实现方式二:使用组件化开发优化案例
root.render中的参数可以是一个HTML元素,也可以是一个组件
所以可以将上面的业务逻辑封装到一个组件中,然后传入到ReactDOM.render函数中
在React中可以以类的方式来封装组件:
- 首先定义一个类去继承React.Component
- 类名必须要大写,小写会被认为是HTML元素
- 定义数据
- 定义的数据可以分为两类:参与页面更新的数据(也就是当数据改变时需要重新渲染页面)、以及不参与界面更新的数据
- 参与界面更新的数据需要定义在当前对象的state中,可以在构造函数中通过this.state = {xxx}的方式来定义
- 当数据发生变化时,可以通过调用this.setState来更新数据,此时React会执行update操作重新调用render函数来更新界面
- 实现当前组件的render函数
- 在render函数中,return的JSX就是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>
</head>
<body>
<div id="app"></div>
<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">
class App extends React.Component {
constructor() {
super()
this.state = {
message: 'Hello World'
}
this.btnClick = this.btnClick.bind(this)
}
btnClick() {
this.setState({
message: 'Hello React'
})
}
render() {
return (
<div>
<h1>{this.state.message}</h1>
<button onClick={this.btnClick}>修改文本</button>
</div>
)
}
}
const app = ReactDOM.createRoot(document.querySelector('#app'))
app.render(<App />)
</script>
</body>
</html>
需要注意的是,绑定的事件的this默认情况下是undefined,需要我们手动绑定一下this
一般会在constructor中统一对事件进行this的绑定,也可以在JSX中绑定,比如:onClick={this.btnClick.bind(this)}
之所以默认情况下this是undefined是由于在class中是开启了严格模式的,而且在我们进行事件绑定时React内部再执行函数时并没有绑定this,所以会是undefined
因为在正常的DOM操作中,监听点击,监听函数中的this其实是节点对象(比如这里的button对象)
而React之所以没绑定this是因为React并不是直接渲染成真实DOM的,我们在JSX中编写的button其实只是一个语法糖,其本质是React的Element对象
所以在这里发生监听的时候,并没有绑定this,在严格模式下没有绑定this默认是undefined,所以需要我们手动绑定一下,不然在执行btnClick的时候this.setState相当于undefined.setState,会报如下错误:
案例二:展示电影列表
<!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="app"></div>
<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">
class App extends React.Component {
constructor() {
super()
this.state = {
movies: ['哪吒', '独行月球', '杨戬']
}
}
btnClick() {
this.setState({
message: 'Hello React'
})
}
render() {
return (
<ul>
{this.state.movies.map(movie => <li>{movie}</li>)}
</ul>
)
}
}
const app = ReactDOM.createRoot(document.querySelector('#app'))
app.render(<App />)
</script>
</body>
</html>
由于JSX中是可以直接展示数组的,所以可以将movies数组直接map成li数组进行展示即可
案例三:计数器
<!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="app"></div>
<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">
class App extends React.Component {
constructor() {
super()
this.state = {
count: 100
}
this.increment = this.increment.bind(this)
this.decrement = this.decrement.bind(this)
}
increment() {
this.setState({ count: this.state.count + 1 })
}
decrement() {
this.setState({ count: this.state.count - 1 })
}
render() {
return (
<div>
<h1>{this.state.count}</h1>
<button onClick={this.increment}>+1</button>
<button onClick={this.decrement}>-1</button>
</div>
)
}
}
const app = ReactDOM.createRoot(document.querySelector('#app'))
app.render(<App />)
</script>
</body>
</html>