代码下载
React 概述
React 是一个用于构建用户界面的javaScript库,起源于facebook的内部项目,后续在13年开源了出来。
特点:
- 声明式——只需要描述UI看起来是什么样式,就跟写HTML一样,React负责渲染UI
- 基于组件——组件时React最重要的内容,组件表示页面中的部分内容,组合、复用多个组件,可以实现完整的页面功能
- 学习一次,随处使用——使用React可以开发Web应用,使用React可以开发移动端,可以开发VR应用
React 基本使用
1、安装 React,在终端执行命令 npm i react react-dom
:
- react 包是核心,提供创建元素、组件等功能
- react-dom 包提供 DOM 相关功能等
2、引入 react 和 react-dom 两个 js 文件:
<script src="../node_modules/react/umd/react.development.js"></script>
<script src="../node_modules/react-dom/umd/react-dom.development.js"></script>
3、创建 React 元素并渲染 React 元素到页面中:
<body>
<div id="root"></div>
</body>
<script>
const title = React.createElement('h2', null, 'React 基本使用')
ReactDOM.createRoot(document.getElementById('root')).render(title)
</script>
React.createElement
方法说明:
- 返回值:React元素
- 第一个参数:要创建的React元素名称
- 第二个参数:该React元素的属性
- 第三个及其以后的参数:该React元素的子节点
const title = React.createElement('h2', null, 'hello, world!', React.createElement('a', { href: 'https://www.baidu.cn/' }, '百度'))
ReactDOM.createRoot(dom).render(el)
方法说明:要渲染的React元素渲染到页面中的指定位置。
React 脚手架
React脚手架意义:
- 脚手架是开发现代Web应用的必备
- 充分利用 Webpack,Babel,ESLint等工具辅助项目开发
- 零配置,无需手动配置繁琐的工具即可使用
- 关注业务,而不是工具配置
React 脚手架初始化项目:
1、初始化项目,命令:npx create-react-app my-app
npx 命令是 npm v5.2.0 引入的一条命令:
- 目的:提升包内提供的命令行工具的使用体验
- 原来:先安装脚手架包,再使用这个包中提供的命令
- 现在:无需安装脚手架包,就可以直接使用这个包提供的命令
2、 启动项目,在项目根目录执行命令:npm start
补充
脚手架创建项目的方式:
1、推荐使用:npx create-react-app my-app
2、npm init react-app my-app
3、如果安装了 yarn,可以使用 yarn create react-app my-app
- yarn 是 Facebook 发布的包管理器,可以看做是 npm 的替代品,功能与 npm 相同
- yarn 具有快速、可靠和安全的特点
- 初始化新项目:yarn init
- 安装包: yarn add 包名称
- 安装项目依赖项: yarn
- 其他命令,请参考yarn文档
在脚手架中使用 React
1、导入 react 和 react-dom 两个包:
import React from 'react'
import ReactDOM from 'react-dom/client'
2、调用 React.createElement() 方法创建 react 元素,并使用 ReactDOM 渲染到页面中:
const title = React.createElement('h2', null, 'hello, world!')
ReactDOM.createRoot(document.getElementById('root')).render(title)
JSX
通过createElement()方法创建的React元素有一些问题,代码比较繁琐,结构不直观,无法一眼看出描述的结构,不优雅。
JSX是JavaScript XML 的简写,表示在JavaScript代码中写HTML格式的代码,JSX优势就是声明式语法更加直观,与HTML结构相同,降低了学习成本,提升开发效率。
使用 JSX 语法创建 react 元素,并使用 ReactDOM 渲染到页面中:
const hiJSX = <h2>hello JSX!</h2>
ReactDOM.createRoot(document.getElementById('hiJSX')).render(hiJSX)
思考,为什么在脚手架中可以使用JSX语法?
- JSX 不是标准的ECMAScript语法,它是ECMAScript的语法拓展
- 需要使用babel编译处理后,才能在浏览器环境中使用
- create-react-app脚手架中已经默认有该配置,无需手动配置
- 编译JSX语法的包: @bable/preset-react
使用 JSX 注意点:
- React元素的属性名使用驼峰命名法。
- 特殊属性名:class -> className、for -> htmlFor、tabindex -> tabIndex 。
- 没有子节点的React元素可以用
/>
结束 。 - 推荐:使用小括号包裹 JSX ,从而避免 JS 中的自动插入分号陷阱。
const note = (
<div className='note' />
)
ReactDOM.createRoot(document.getElementById('note')).render(note)
JSX 中使用 JavaScript 表达式
JSX 嵌入 JS 表达式,语法是 { JavaScript表达式 }
,注意事项如下:
- 单大括号中可以使用任意的 JavaScript 表达式
- JSX 自身也是 JS 表达式
- JS 中的对象是一个例外,一般只会出现在 style 属性中
- 不能在
{}
中出现语句(比如:if/for 等)
const sayHi = () => {
return 'hello, JSX'
}
const expression = (
<div>
<p>{1}</p>
<p>{'a'}</p>
<p>{1 + 7}</p>
<p>1 大于 2 吗?{1 > 2 ? '大于' : '小于或等于'}</p>
<p>{sayHi()}</p>
{/* 错误示例 */}
{/* <p>{{ a: 'a' }}</p> */}
{/* <p>{if (true) {}}</p> */}
{/* <p>{for (var i = 0; i < 8; i++) {}}</p> */}
</div>
)
ReactDOM.createRoot(document.getElementById('expression')).render(expression)
JSX 条件渲染
根据条件渲染特定的 JSX 结构,可以使用if/else或三元运算符或逻辑与运算符来实现:
var flag = true
// if-else
const load = () => {
if (flag) {
return <p>loading……</p>
} else {
return <p>加载完成</p>
}
}
// 三元表达式
const load1 = () => {
return flag ? <p>loading……</p> : <p>加载完成</p>
}
// 逻辑与运算符
const load2 = () => {
return flag && <p>loading……</p>
}
const condition = (
<div>
if-else 条件语句:
{load()}
三元表达式:
{load1()}
逻辑与运算符:
{load2()}
</div>
)
ReactDOM.createRoot(document.getElementById('condition')).render(condition)
JSX 列表渲染
如果要渲染一组数据,应该使用数组的 map() 方法:
- 渲染列表时应该添加 key 属性,key 属性的值要保证唯一
- map() 遍历谁,就给谁添加 key 属性
- 尽量避免使用索引号作为 key
const data = [
{ id: 0, name: 'one' },
{ id: 1, name: 'two' },
{ id: 2, name: 'three' },
{ id: 3, name: 'four' }
]
const ul = (
<ul>
{data.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
)
ReactDOM.createRoot(document.getElementById('list')).render(ul)
JSX 的样式处理
1、行内样式 —— style,在style里面通过对象的方式传递数据,这种方式比较的麻烦,不方便进行阅读,而且还会导致代码比较的繁琐:
const lineStyle = (
<div style={{ backgroundColor: 'red', width: '100px', height: '100px'}}></div>
)
ReactDOM.createRoot(document.getElementById('lineStyle')).render(lineStyle)
2、类名 —— className(推荐),先创建CSS文件编写样式代码,然后在js中进行引入并设置类名即可:
/* css */
.green {
background-color: green;
width: 100px;
height: 100px;
}
// js
import './index.css'
const className = (
<div className='green'></div>
)
ReactDOM.createRoot(document.getElementById('className')).render(className)
总结:React完全利用JS语言自身的能力来编写UI,而不是造轮子增强HTML功能。
React 组件基础
组件是React的一等公民,使用React就是在用组件。组件表示页面中的部分功能,组合多个组件实现完整的页面功能。特点是可复用、独立、可组合。
React 组件的两种创建方式
1、使用函数创建组件,使用 JS 的函数(或箭头函数)创建的组件
- 函数名称必须以大写字母开头
- 函数组件必须有返回值,表示该组件的结构
- 如果返回值为 null,表示不渲染任何内容
// 渲染结构的函数组件
function FuncComOne() {
return (
<div>渲染结构的函数组件</div>
)
}
ReactDOM.createRoot(document.getElementById('funcOne')).render(<FuncComOne></FuncComOne>)
// 不渲染结构的函数组件
function FuncComTwo() {
return null
}
ReactDOM.createRoot(document.getElementById('funcTwo')).render(<FuncComTwo></FuncComTwo>)
// 使用箭头函数
const FuncComThree = () => <div>使用箭头函数创建渲染结构的函数组件</div>
ReactDOM.createRoot(document.getElementById('funcThree')).render(<FuncComThree></FuncComThree>)
渲染函数组件用函数名作为组件标签名,组件标签可以是单标签也可以是双标签。
说明:
- 使用JS中的函数创建的组件叫做函数组件
- 函数组件必须有返回值
- 组件名称必须以大写字母开头, React 据此区分 组件 和 普通的React 元素
- 使用函数名作为组件标签名
2、使用类创建组件就是使用 ES6 的 class 创建的组件
- 类名称也必须以大写字母开头
- 类组件应该继承 React.Component 父类,从而可以使用父类中提供的方法或属性
- 类组件必须提供 render() 方法
- render() 方法必须有返回值,表示该组件的结构
// 使用类创建组件
class ClassCom extends React.Component {
render() {
return (
<div>渲染结构的类组件</div>
)
}
}
ReactDOM.createRoot(document.getElementById('classCom')).render(<ClassCom></ClassCom>)
抽离为独立 JS 文件
组件作为一个独立的个体,一般都会放到一个单独的 JS 文件中。
1、创建组件文件 SingleCom.js,在 SingleCom.js 中导入React创建组件(函数 或 类)并导出该组件:
import React from "react";
class SingleCom extends React.Component {
render() {
return (
<div>把组件抽离为独立 JS 文件</div>
)
}
}
export default SingleCom
2、在 index.js 中导入 Hello 组件并渲染组件:
import SingleCom from './SingleCom'
ReactDOM.createRoot(document.getElementById('SingleCom')).render(<SingleCom></SingleCom>)
React 事件处理
React 事件绑定语法与 DOM 事件语法相似,语法是 on+事件名称={事件处理程序},比如:onClick={() => {}}
注意:React 事件采用驼峰命名法,比如:onMouseEnter、onFocus
事件对象,可以通过事件处理程序的参数获取到事件对象。React 中的事件对象叫做:合成事件(对象),兼容所有浏览器,无需担心跨浏览器兼容性问题。
// 函数组件事件处理
function FuncComEvent() {
function clickHandle(e) {
e.preventDefault()
console.log('点我干嘛');
}
return (
<a href='https://www.baidu.cn/' onClick={clickHandle}>点我</a>
)
}
ReactDOM.createRoot(document.getElementById('funcComEvent')).render(<FuncComEvent></FuncComEvent>)
// 类组件事件处理
class ClassComEvent extends React.Component {
clickHandle(e) {
e.preventDefault()
console.log('点我干嘛');
}
render() {
return (
<a href='https://www.baidu.cn/' onClick={this.clickHandle}>点我</a>
)
}
}
ReactDOM.createRoot(document.getElementById('classComEvent')).render(<ClassComEvent></ClassComEvent>)
有状态组件和无状态组件
函数组件又叫做无状态组件,类组件又叫做有状态组件,状态(state)即数据:
- 函数组件没有自己的状态,只负责数据展示(静)
- 类组件有自己的状态,负责更新 UI,让页面“动” 起来
状态(state)即数据,是组件内部的私有数据,只能在组件内部使用,state 的值是对象,表示一个组件中可以有多个数据:
- 通过
this.state
来获取状态。 - 状态是可变的,通过
this.setState({ 要修改的数据 })
来修改状态,setState() 的作用是修改 state 并更新UI,此思想是数据驱动视图。
注意:不要直接修改 state 中的值,这是错误的!!!
JSX 中掺杂过多 JS 逻辑代码,会显得非常混乱。推荐将逻辑抽离到单独的方法中,保证 JSX 结构清晰。
注意:事件处理程序中 this 的值为 undefined。希望 this 指向组件实例(render方法中的this即为组件实例)。
class StateCom extends React.Component {
// 构造方法
// constructor() {
// super()
// this.state = {
// count: 0
// }
// }
// 简写,不使用构造方法
state = {
count: 0
}
// 事件处理程序
onDecrease() {
// this 为 undefined
this.setState({ count: this.state.count -1 })
}
render() {
// render 方法 this 为组件实例
return (
<div>
<p>数据结果:{this.state.count}</p>
<button onClick={() => {
// 箭头函数 this 为定义时的 this 即为组件实例
// 修改 state
this.setState({ count: this.state.count + 1 })
// 错误修改 state 的方法
// this.state.count++
}}>+1</button>
{/* 从 JSX 中抽离事件处理程序 */}
<button onClick={this.onDecrease}>-1</button>
</div>
)
}
}
ReactDOM.createRoot(document.getElementById('stateCom')).render(<StateCom></StateCom>)
事件绑定 this 指向
1、利用箭头函数自身不绑定this的特点,render() 方法中的 this 为组件实例,可以获取到 setState()。
2、利用ES5中的bind方法,将事件处理程序中的this与组件实例绑定到一起。
3、利用箭头函数形式的class实例方法,该语法是实验性语法,但是由于babel的存在可以直接使用。
// 处理 事件中 this 指向
class EventThis extends React.Component {
constructor() {
super()
this.state = {
count: 0
}
// 2、事件绑定 this 指向
this.onIncreaceTwo = this.onIncreaceTwo.bind(this)
}
onIncreaceOne() {
this.setState({ count: this.state.count + 1 })
}
onIncreaceTwo() {
this.setState({ count: this.state.count + 2 })
}
// 3、class 的实例方法
onIncreaceThree = () => {
this.setState({ count: this.state.count + 3 })
}
render() {
return (
<div>
<p>结果:{this.state.count}</p>
{/* 1、通过箭头函数 */}
<button onClick={() => this.onIncreaceOne()}>+1</button>
<button onClick={this.onIncreaceTwo}>+2</button>
<button onClick={this.onIncreaceThree}>+3</button>
</div>
)
}
}
ReactDOM.createRoot(document.getElementById('eventThis')).render(<EventThis></EventThis>)
推荐使用class的实例方法。
表单处理
受控组件
HTML 中的表单元素是可输入的,也就是有自己的可变状态,而 React 中可变状态通常保存在 state 中,并且只能通过 setState() 方法来修改。React 将 state 与表单元素值value绑定到一起,由 state 的值来控制表单元素的值。
受控组件就是其值受到 React 控制的表单元素,处理受控组件步骤:
- 在 state 中添加一个状态,作为表单元素的value值(控制表单元素值的来源)
- 给表单元素绑定 change 事件,将 表单元素的值 设置为 state 的值(控制表单元素值的变化)
// 受控组件
class ControlCom extends React.Component {
constructor() {
super()
this.state = {
title: '',
content: '',
city: 'bj',
isChecked: 'true'
}
}
// 处理 文本框、富文本框、下拉框、复选框 的变化
titleChange = e => this.setState({ title: e.target.value })
contentChange = e => this.setState({ content: e.target.value })
cityChange = e => this.setState({ city: e.target.value })
isCheckedChange = e => this.setState({ isChecked: e.target.checked })
render() {
return (
<div>
<input type='text' placeholder='请输入标题' value={this.state.title} onChange={this.titleChange}></input>
<br/>
<textarea placeholder='请输入内容' value={this.state.content} onChange={this.contentChange}></textarea>
<br/>
<select value={this.state.city} onChange={this.cityChange}>
<option value='bj'>北京</option>
<option value='sh'>上海</option>
<option value='sz'>深圳</option>
<option value='gz'>广州</option>
</select>
<br/>
<input type='checkbox' checked={this.state.isChecked} onChange={this.isCheckedChange}></input>
</div>
)
}
}
ReactDOM.createRoot(document.getElementById('controlCom')).render(<ControlCom></ControlCom>)
受控组件多表单元素优化
个表单元素都有一个单独的事件处理程序处理太繁琐,可以使用一个事件处理程序同时处理多个表单元素,步骤如下:
- 给表单元素添加name属性,名称与 state 相同
- 根据表单元素类型获取对应值
- 在 change 事件处理程序中通过 [name] 来修改对应的state
// 受控组件
class ControlCom extends React.Component {
constructor() {
super()
this.state = {
title: '',
content: '',
city: 'bj',
isChecked: true
}
}
valueChage = e => {
const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value
this.setState({ [e.target.name]: value })
}
render() {
return (
<div>
<p>优化受控组件</p>
<input name='title' type='text' placeholder='请输入标题' value={this.state.title} onChange={this.valueChage}></input>
<br/>
<textarea name='content' placeholder='请输入内容' value={this.state.content} onChange={this.valueChage}></textarea>
<br/>
<select name='city' value={this.state.city} onChange={this.valueChage}>
<option value='bj'>北京</option>
<option value='sh'>上海</option>
<option value='sz'>深圳</option>
<option value='gz'>广州</option>
</select>
<br/>
<input name='isChecked' type='checkbox' checked={this.state.isChecked} onChange={this.valueChage}></input>
</div>
)
}
}
ReactDOM.createRoot(document.getElementById('controlCom')).render(<ControlCom></ControlCom>)
非受控组件
借助于 ref,使用原生 DOM 方式来获取表单元素值,ref 的作用就是获取 DOM 或组件。使用步骤
- 调用 React.createRef() 方法创建一个 ref 对象
- 将创建好的 ref 对象添加到文本框中
- 通过 ref 对象获取到文本框的值
// 非受控组件
class UncontrolCom extends React.Component {
constructor() {
super()
// 1、调用 React.createRef() 方法创建一个 ref 对象
this.textRef = React.createRef()
}
render() {
return (
<div>
{/* 2、将创建好的 ref 对象添加到文本框中 */}
<input type='text' ref={this.textRef}></input>
<button onClick={this.getText}>获取文本</button>
</div>
)
}
// 3、 通过 ref 对象获取到文本框的值
getText= () => {
console.log('text: ', this.textRef.current.value);
}
}
ReactDOM.createRoot(document.getElementById('uncontrolCom')).render(<UncontrolCom></UncontrolCom>)
评论列表案例
class Comment extends React.Component {
// 初始化状态
state = {
count: 10,
comments: [
{ id: 1, name: 'jack', content: '沙发!!!' },
{ id: 2, name: 'rose', content: '板凳~' },
{ id: 3, name: 'tom', content: '楼主好人' }
],
userName: '',
userContent: ''
}
// 渲染评论列表:
renderList() {
const { comments } = this.state
if (comments.length === 0) {
return <div className='no-comment'>暂无评论,快去评论吧</div>
}
return comments.map(item => (
<li key={item.id}>
<h4>评论人:{item.name}</h4>
<p>评论内容:{item.content}</p>
</li>
))
}
// 处理表单元素
formHandle = (e) => {
const { name, value } = e.target
this.setState({
[name]: value
})
}
// 添加评论
addComment = () => {
const { userName, userContent } = this.state
if (userName.trim() === '' || userContent.trim() === '') {
alert('请输入评论人和评论内容!')
return
}
const newComments = [{
id: Math.random(),
name: userName,
content: userContent
}, ...this.state.comments]
this.setState({
comments: newComments,
userName: '',
userContent: ''
})
}
render () {
const { userName, userContent } = this.state
return (
<div className='comment'>
<div>
<input className='user' type='text' placeholder='请输入评论人' value={userName} name='userName' onChange={this.formHandle}></input>
<br />
<textarea className='content' cols='30' rows='10' placeholder='请输入评论内容' value={userContent} name='userContent' onChange={this.formHandle}></textarea>
<br />
<button onClick={this.addComment}>发表评论</button>
{this.renderList()}
</div>
</div>
)
}
}
ReactDOM.createRoot(document.getElementById('comment')).render(<Comment></Comment>)
实现步骤:
1、渲染评论列表:
- 在 state 中初始化评论列表数据
- 使用数组的map方法遍历state中的列表数据
- 给每个被遍历的li元素添加key属性
- 渲染暂无评论:
- 判断列表数据的长度是否为0
- 如果为0,则渲染暂无评论
3、获取评论信息:使用受控组件方式处理表单元素
4、发表评论:
- 给按钮绑定单击事件
- 在事件处理程序中,通过state获取评论信息
- 将评论信息添加到state中,并调用 setState() 方法更新state
- 边界情况:清空文本框
- 边界情况:非空判断