技术特点
非技术方面
- 由facebook来维护和更新,它是大量优秀程序员的思想结晶;
- react hooks是开创性的新功能;
- vue composition api学习react hooks的思想;
技术方面
- 声明式---它允许只需要维护自己的状态,当状态改变时,React可以根据最新的状态去渲染UI界面
- 组件化开发---复杂页面拆分成一个个小组件
- 跨平台---Web、ReactNative(或Flutter)、ReactVR
三个开发依赖
react开发必须需要3个库:
- react---包含react所必须的核心代码
- react-dom---react渲染在不同平台所需要的核心代码
- babel---将jsx转换成浏览器识别的代码的工具
为什么需要react-dom这个库呢?
- web端:react-dom会将jsx最终渲染成真实DOM,显示在浏览器中
- native端:react-dom会将jsx最终渲染成原生的控件,比如android和ios的按钮
babel和react的关系
- 可以使用React.createElement来编写js代码,但是非常繁琐,且可读性差;
- 而jsx(JavaScript XML)的语法可以克服以上缺点;
- 但浏览器不能识别jsx这种高级语法,需要babel进行转换成普通js;
hello案例
<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>
<!-- babel -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
// script要写上type属性,需要转化代码
// React18以前
// ReactDOM.render(<h2>Hello World</h2>, document.querySelector('#root'))
// 18之后
const root = ReactDOM.createRoot(document.querySelector('#root'))
// 1.文本定义成变量
let msg = 'Hello World'
// 2.监听按钮的点击
function btnClick() {
// 2.1修改数据
msg = 'React'
// 2.2重新渲染界面
rootRender()
))
}
rootRender()
// 3.封装一个渲染函数
function rootRender() {
root.render((
<div>
<h2>{msg}</h2>
<button onClick={btnClick}>修改文本</button>
</div>
))
}
jsx
jsx是一种JavaScript的语法拓展(eXtension),很多地方称之为JavaScript XML,因为看起来就是一段XML语法;
它用于描述UI界面,并且其可以完成和JavaScript融合在一起使用;
它不同于Vue中的模板语法,不需要学习模板语法中的一些指令(比如v-for、v-if、v-else、v-bind);
class App extends React.Component {
// 组件数据
constructor() {
super()
this.state = {
counter: 0
}
}
// 方法
// 渲染内容 render方法
render() {
const { counter } = this.state
const msg = <h2>当前计数:{counter}</h2>
return msg
}
}
// 创建root并渲染App组件
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<App/>)
为什么React选择JSX而不是像vue一样搞一个模板语法?
react认为渲染逻辑本质上与其它UI逻辑存在内在耦合;
- 比如UI需要绑定事件;
- 比如UI中需要展示状态;
- 比如在某些状态发生改变时,又需要改变UI;
书写规范
- 顶层只能有一个根元素,所以很多时候外层包裹一个div(或Fragment);
- 为了方便阅读,通常在最外层包一个小括号;
- 单标签必须以/>结尾;
- 注释写法
{ /* 注释 */ }
class App extends React.Component {
// 组件数据
constructor() {
super()
this.state = {
counter: 0
}
}
// 方法
// 渲染内容 render方法
render() {
{ /* 注释 */ }
const { counter } = this.state
const msg = <h2>当前计数:{counter}</h2>
return msg
}
}
// 创建root并渲染App组件
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<App/>)
嵌入内容
插入变量为子元素时
- 若是Number、String、Array类型时,可以直接显示;
- 若是null、undefined、Boolean类型时,内容为空,要想显示需要转换为字符串;
- object对象类型不能作为子元素(not valid as a react child)
插入表达式时
类似插值表达式
- 运算表达式
- 三元运算符
- 执行一个函数
绑定属性
- class绑定尽量使用className,因为在jsx中class是关键字(有警告);
- 有动态类可以使用字符串拼接、数组动态添加、第三方库classnames等等;
- 绑定style属性:绑定对象类型;
constructor() {
super()
this.state = {
title: 'hhh',
isActive: true
}
}
// 方法
// 渲染内容 render方法
render() {
const { title, isActive } = this.state
// 1.class绑定写法一:字符串拼接
const className = `abc cba ${isActive ? 'active' : ''}`
// 2.class绑定写法二:将所有的class放数组中
const classList = ['abc', 'cba']
if(isActive) classList.push('active')
return (
<div>
<h2 title={title} className={className}>123</h2>
<h2 className={classList.join(' ')}>123</h2>
<h2 style={{color: "red", fontSize: "30px"}}>ggg</h2>
</div>
)
}
事件绑定
原生DOM有个监听事件,可以如何操作?
- 获取节点,添加监听事件
- 节点上绑定onxxx
在React中是如何操作的呢?
- 事件命名采用小驼峰(camelCase);
- 通过 {} 传入事件处理函数,这函数会在事件发生时被执行;
this的绑定问题
- 主动修改this指向,显式绑定
- es6 class yields
- 直接传入箭头函数
方法在哪里定义?
class App extends React.Component {
// 组件数据
constructor() {
super()
this.state = {
msg: 'hello'
}
}
// 组件方法
btnClick() {
console.log(this) // undefined
}
// 渲染内容 render方法
render() {
return (
<div>
<h2>{this.state.msg}</h2>
<button onClick={this.btnClick}>修改文本</button>
</div>
)
}
}
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<App/>)
onClick={this.btnClick}
等效于
const click = this.btnClick
click()
由于类中代码会使用严格模式,独立调用的函数中this指向undefined
如何将this指向当前对象实例? 显式绑定
class App extends React.Component {
// 组件数据
constructor() {
super()
this.state = {
msg: 'hello'
}
}
// 组件方法
btnClick() {
console.log(this) // undefined
}
// 渲染内容 render方法
render() {
return (
<div>
<h2>{this.state.msg}</h2>
<button onClick={this.btnClick.bind(this)}>修改文本</button>
</div>
)
}
}
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<App/>)
render函数中的this指向的便是当前对象的实例
onClick={this.btnClick.bind(this)}
等效于
const click = this.btnClick.bind(this)
click()
综上,
class App extends React.Component {
// 组件数据
constructor() {
super()
this.state = {
msg: 'hello'
}
}
// 组件方法
btnClick() {
this.setState({
msg: 'React'
})
}
// 渲染内容 render方法
render() {
return (
<div>
<h2>{this.state.msg}</h2>
<button onClick={this.btnClick.bind(this)}>修改文本</button>
</div>
)
}
}
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<App/>)
还可以这样修改this,提前在constructor里修改this指向,这样使用时会方便一点,不用每次都要写bind
class App extends React.Component {
// 组件数据
constructor() {
super()
this.state = {
msg: 'hello'
}
this.btnClick = this.btnClick.bind(this)
}
// 组件方法
btnClick() {
this.setState({
msg: 'React'
})
}
// 渲染内容 render方法
render() {
return (
<div>
<h2>{this.state.msg}</h2>
<button onClick={this.btnClick}>修改文本</button>
</div>
)
}
}
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<App/>)
setState()来自哪里呢?
继承自React.Component,其内部完成了两件事:
- 将state中指定的值修改掉(这里是msg);
- 自动重新执行render函数;
es6 class yields 方式
// 利用es6的class yields语法,类中也可以给成员赋值
btnClick = () => {
console.log(this) // 当前对象实例
}
// 渲染内容 render方法
render() {
const { btnClick } = this
return (
<div>
<button onClick={btnClick}></button>
</div>
)
}
直接传入箭头函数
- 当事件触发时,会调用该箭头函数;
- 而该箭头函数里面又可以调用一个函数;
btnClick = () => {
console.log(this) // 当前对象实例
}
// 渲染内容 render方法
render() {
const { btnClick } = this
return (
<div>
<button onClick={() => btnClick()}></button>
</div>
)
}
参数传递问题
虽然bind那种方式也可以传递参数,但是会有参数顺序的问题;
所以使用箭头函数好一点;
// 利用es6的class yields语法,类中也可以给成员赋值
btnClick = (event, name, age) => {
console.log(event) // 当前对象实例
console.log(name)
console.log(age)
}
// 渲染内容 render方法
render() {
const { btnClick } = this
return (
<div>
<button onClick={(e) => btnClick(e, 'zsf', 18)}></button>
</div>
)
}
条件渲染
- 条件判断语句(逻辑较多的情况)
- 三元运算符(简单逻辑)
- 与运算符&&(条件成立渲染某个组件,不成立什么也不渲染)
jsx转化js本质
每遇到一个标签,就会调用React.createElement(type, config, ...children)
参数type:
- 若是标签元素,使用字符串,如 ’div‘ ;
- 若是组件元素,使用组件名,如 login;
参数config:
- 所有jsx中的属性都在config中以键值对的形式存在,比如className属性;
参数children:
- 存放在元素中的内容,以children数组的方式进行存储;
复制一段jsx代码去babel官网转化
jsx
<div>
<h2>{this.state.msg}</h2>
<button onClick={this.btnClick}>修改文本</button>
</div>
js
"use strict";
/*#__pure__*/React.createElement("div", null,
/*#__pure__*/React.createElement("h2", null, (void 0).state.msg),
/*#__pure__*/React.createElement("button", { onClick: (void 0).btnClick },"\u4FEE\u6539\u6587\u672C"));
其中
/*#__pure__*/
pure是 “纯” 的意思,表示后面的函数是纯函数;
由于纯函数没有副作用(不会影响其它作用域的内容),在用不上的时候,tree shaking时可以放心摇掉;
虚拟DOM
通过React.createElement最终创建出来一个ReactElement对象;
一个个ReactElement对象组成JavaScript对象树;
这个对象树就是虚拟DOM;
虚拟DOM有什么作用?
- 可以快速进行diff算法,更新节点;
- 它只是js对象,渲染成什么真实节点由平台决定,跨平台;
- 声明式编程,你只需要告诉React希望UI是什么状态,不需要直接进行DOM操作,从手动修改DOM、属性操作、事件处理中解放出来
协调
可以通过ReactDOM.render让虚拟DOM和真实DOM的同步起来,这个过程叫协调;
列表案例
// 组件数据
constructor() {
super()
this.state = {
list: [1, 2, 3, 4],
currentIndex: 0
}
}
//
btnClick = (index) => {
this.setState({
currentIndex: index
})
}
// 渲染内容 render方法
render() {
const { list, currentIndex } = this.state
const { btnClick } = this
return (
<div>
<ul>
{
list.map((item, index) => {
return (
<li
className={currentIndex === index ? 'active' : ''}
key={item}
onClick={() => btnClick(index)}
>
{item}
</li>
)
})
}
</ul>
</div>
)
}
计数器案例
class App extends React.Component {
// 组件数据
constructor() {
super()
this.state = {
counter: 0
}
this.increment = this.increment.bind(this)
this.decrement = this.decrement.bind(this)
}
// 方法
increment() {
this.setState({
counter: this.state.counter + 1
})
}
decrement() {
this.setState({
counter: this.state.counter - 1
})
}
// 渲染内容 render方法
render() {
const { counter } = this.state
const { increment, decrement } = this
return (
<div>
<h2>当前计数:{counter}</h2>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
)
}
}
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<App/>)
购物车案例
数据源
const books = [
{
id: 1,
name: '《算法导论》',
date: '2006-9',
price: 85.00,
count: 1
},
{
id: 2,
name: '《UNIX编程艺术》',
date: '2006-2',
price: 59.00,
count: 1
},
{
id: 3,
name: '《编程珠玑》',
date: '2008-10',
price: 39.00,
count: 1
},
{
id: 4,
name: '《代码大全》',
date: '2006-3',
price: 128.00,
count: 1
},
]
组件数据
// 组件数据
constructor() {
super()
this.state = {
books: books
}
}
组件方法
// 总价
getTotalPrice() {
return this.state.books.reduce((preValue, item) => preValue + item.count * item.price, 0)
}
// 增加/减少
changeCount(index, count) {
// react不推荐直接修改state中的数据,推荐做法是浅拷贝
const newBooks = [...this.state.books]
newBooks[index].count += count
// 修改state,重新执行render函数
this.setState({ books: newBooks })
}
// 删除一条数据
removeItem(index) {
const newBooks = [...this.state.books]
newBooks.splice(index, 1)
// 修改state,重新执行render函数
this.setState({ books: newBooks })
}
渲染函数
// 有书时的渲染内容
renderBookList() {
const { books } = this.state
return (
<div>
<table>
<thead>
<tr>
<th>序号</th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{
books.map((item, index) => {
return (
<tr key={index}>
<td>{index + 1}</td>
<td>{item.name}</td>
<td>{item.date}</td>
<td>{'¥' + item.price.toFixed(2)}</td>
<td>
<button
disabled={item.count <= 1}
onClick={() => this.changeCount(index, -1)}
>
-
</button>
{item.count}
<button onClick={() => this.changeCount(index, 1)}>+</button>
</td>
<td>
<button onClick={() => this.removeItem(index)}>删除</button>
</td>
</tr>
)
})
}
</tbody>
</table>
<h2>总价格:{'¥' + this.getTotalPrice().toFixed(2)}</h2>
</div>
)
}
// 无书时的渲染内容
renderBookEmpty() {
return <div><h2>购物车为空,请添加书籍</h2></div>
}
// 渲染内容 render方法
render() {
const { books } = this.state
return books.length ? this.renderBookList() : this.renderBookEmpty()
}