import{_ as s,o as n,c as a,a as l}from"./app.ab8d0b9f.js";const A=JSON.parse('{"title":"React","description":"","frontmatter":{},"headers":[{"level":2,"title":"邂逅React","slug":"邂逅react","link":"#邂逅react","children":[{"level":3,"title":"React开发依赖","slug":"react开发依赖","link":"#react开发依赖","children":[]},{"level":3,"title":"Babel与React的关系","slug":"babel与react的关系","link":"#babel与react的关系","children":[]},{"level":3,"title":"React初体验","slug":"react初体验","link":"#react初体验","children":[]},{"level":3,"title":"第一个React程序","slug":"第一个react程序","link":"#第一个react程序","children":[]},{"level":3,"title":"组件化开发","slug":"组件化开发","link":"#组件化开发","children":[]},{"level":3,"title":"提前绑定this","slug":"提前绑定this","link":"#提前绑定this","children":[]},{"level":3,"title":"列表渲染","slug":"列表渲染","link":"#列表渲染","children":[]},{"level":3,"title":"计数器案例","slug":"计数器案例","link":"#计数器案例","children":[]}]},{"level":2,"title":"认识JSX语法","slug":"认识jsx语法","link":"#认识jsx语法","children":[]}],"relativePath":"note/React.md","lastUpdated":1681575540000}'),p={name:"note/React.md"},o=l(`
react
包含React的核心代码react-dom
将React渲染到不同平台需要的核心代码babel
将JSX转换成React代码的工具为什么要拆分成这么多的包?
react
包中包含了 React Web 和 React Native 共同拥有的核心代码react-dom
针对Web和Native完成的事情不同 react-dom
会将JSX渲染成真实DOM,展示在浏览器中react-dom
会将JSX渲染成原生的控件(如Android中的Button,iOS中的UIButton)Babel是什么?
二者之间的联系
我们通过CDN方式引入react、react-dom、babel这三个依赖
并且创建#root
根节点,作为渲染React组件的容器,再新建一个script标签,键入以下内容
<div id="root"></div>
<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" crossorigin></script>
<script type="text/babel">
ReactDOM.render(<div>Hello, React!</div>, document.querySelector('#root'))
</script>
这时,一个内容为Hello, React!
的div标签就被渲染到页面上了
需要注意的是:ReactDOM.render
这种写法适用于React18之前,在React18之后建议用下面的代码渲染根节点:
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<h1>Hello, React!</h1>)
设想我们现在有这样一个需求:点击按钮使文本Hello, World!
变为Hello, React!
我们很容易就能写出如下代码:
const root = ReactDOM.createRoot(document.querySelector('#root'))
let msg = 'Hello, World!'
render() // initial render
function handleChangeClick() {
msg = 'Hello, React!'
}
root.render(
<div>
<h1>{msg}</h1>
<button onClick={handleChangeClick}>Change Text</button>
</div>
)
在Vue中,如果我们对数据进行了修改,Vue的数据响应式会自动帮我们完成视图的更新
然而在React中,当我们修改了数据需要通知React,让React重新渲染视图。在这里,我们可以把渲染的过程封装为一个函数,方便我们重复调用,触发重新渲染
const root = ReactDOM.createRoot(document.querySelector('#root'))
let msg = 'Hello, World!'
render() // initial render
function handleChangeClick() {
msg = 'Hello, React!'
render() // re-render
}
function render() {
root.render(
<div>
<h1>{msg}</h1>
<button onClick={handleChangeClick}>Change Text</button>
</div>
)
}
这个案例中,我们使用{}
语法,将动态的JS语法嵌入到JSX代码中
React有两种组件:类组件与函数组件,React18+推荐使用函数组件+Hooks
我们使用类组件来逐步重构上面的案例:
class App extends React.Component {
constructor() {
super()
this.state = {
msg: 'Hello, World!'
}
}
render() {
return <h2>{this.state.msg}</h2>
}
}
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<App />)
类组件必须实现render方法,render方法返回值为后续React渲染到页面的内容
组件内数据分为两类
需要触发视图重新渲染的数据,我们将其成为:参与数据流
state
属性中this.state = { name: 'Ziu' }
来定义状态this.setState
来更新数据,通知React执行视图更新:::success 需要注意的是,在constructor中我们调用了super
,因为App类是继承自React.Component类,调用super
即调用了其父类的构造函数,让我们的App组件可以继承一些内置属性/方法如state setState render
:::
至此我们完成了数据的迁移,下面我们来完成事件函数的迁移。
有了之前的介绍,我们很容易的可以写出下面的代码:
class App extends React.Component {
constructor() {
super()
this.state = {
msg: 'Hello, World!'
}
}
changeText() {
this.setState({
msg: 'Hello, React!'
})
}
render() {
return (
<div>
<h2>{this.state.msg}</h2>
<button onClick={this.changeText}>Change Text</button>
</div>
)
}
}
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<App />)
我们可以写一个实例方法changeText来修改msg,然而,此时我们点击按钮后发现,案例不能正常工作。
如果在changeText中打log,会发现函数被正常触发了,但是状态没有更新
为什么this.setState失效了?这和this的绑定有关:绑定的changeText
在被调用时,向上找this
找到的是全局的this
即undefined
这种情况有点类似于下面的例子:
const app = new App()
app.changeText() // this => app
const func = app.changeText
func() // this => undefined
在非严格模式下,直接调用func时的this指向的是window,严格模式下则为undefined
为了解决this绑定的问题,我们需要显式把函数调用绑定给当前组件,这时组件就可以正常工作了。
class App extends React.Component {
constructor() {
super()
this.state = {
msg: 'Hello, World!'
}
}
changeText() {
this.setState({
msg: 'Hello, React!'
})
}
render() {
return (
<div>
<h2>{this.state.msg}</h2>
<button onClick={this.changeText.bind(this)}>Change Text</button>
</div>
)
}
}
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<App />)
在render函数中频繁通过.bind
毕竟不太优雅,好在也有另一种方式:可以在constructor中提前对实例方法进行this绑定:
...
constructor() {
super()
this.state = {
msg: 'Hello, World!'
}
this.changeText = this.changeText.bind(this)
}
render() {
...
<button onClick={this.changeText}>Change Text</button>
...
}
...
可以通过循环,将数组渲染到视图中
class App extends React.Component {
constructor() {
super()
this.state = {
movieList: [
'The Shawshank Redemption',
'The Godfather',
'The Godfather: Part II',
'The Dark Knight'
]
}
}
render() {
return (
<ul>
{this.state.movieList.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
)
}
}
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<App />)
需要注意的是,这里绑定的key的功能类似于Vue中的特殊属性key,它用来帮助React对列表渲染进行更高效的更新。
结合之前的知识,可以实现一个简单的计数器
class App extends React.Component {
constructor() {
super()
this.state = {
count: 0
}
this.addCount = this.addCount.bind(this)
this.subCount = this.subCount.bind(this)
}
addCount() {
this.setState({
count: this.state.count + 1
})
}
subCount() {
this.setState({
count: this.state.count - 1
})
}
render() {
const { count } = this.state
return (
<div>
<h1>Count: {count}</h1>
<button onClick={this.addCount}>Add</button>
<button onClick={this.subCount}>Sub</button>
</div>
)
}
}
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<App />)
是因为我们给script标签添加了type="text/babel"
属性,浏览器不会对这个script进行解析,当babel被加载完成后,babel会在页面中寻找type="text/babel"
的script标签进行转义,转义后的代码才会被浏览器执行