React 基础
认识 React
- React 中文文档1(国内社区):https://react.docschina.org/
- React 中文文档2(官方):https://zh-hans.reactjs.org
React 概述
React 是一个用于构建用户界面
的 JavaScript 库。
React 起源于
React 本身只关注界面, 其它如:前后台交互、路由管理、状态管理等都由其扩展插件或其它第三方插件搞定
React 几个特点
-
声明式编码
-
组件化编码
-
将一个较大较复杂的界面拆分成几个可复用的部分封装成多个组件, 再组合使用
-
组件可以被反复使用
-
-
引入了 JSX 语法,编写高效
-
内置 diff 算法,运行高效
-
一次学习,随处编写
- 不仅可以开发 web 应用(react-dom),还可以开发原生安卓或 IOS 应用(react-native)
React 开发的网站
- MDN: https://developer.mozilla.org/zh-CN/
- 知乎: https://www.zhihu.com/
- 阿里云: https://www.aliyun.com/
React基本使用
基本使用步骤
-
引入两个JS文件( 注意引入顺序 )
<!-- react库, 提供React对象 --> <script src="../js/react.development.js"></script> <!-- react-dom库, 提供了ReactDOM对象 --> <script src="../js/react-dom.development.js"></script>
-
在html定义一个根容器标签
<div id="root"></div>
-
创建react元素(类似html元素)
// 返回值:React元素 // 参数1:要创建的React元素名称 =》字符串 // 参数2:元素的属性 =》对象 {id: 'box'} 或者 null // 后面参数:该React元素的所有子节点 =》文本或者其他react元素 const element = React.createElement( 'h1', {title: '你好, React!'}, 'Hello React!' )
-
渲染 react 元素
// 渲染React元素到页面容器div中 ReactDOM.render(element, document.getElementById('root'))
特殊属性
- class ==> className
const element = React.createElement(
'h1',
{
title: '你好, React!',
className: 'active'
},
'Hello React!'
)
再来个复杂点的
const title = '北京疫情'
const content = '北京这段时间疫情还在持续中...'
const vDom = React.createElement('div', null,
React.createElement('h2', {title}, '你关注的北京疫情'),
React.createElement('p', null, content)
)
ReactDOM.render(vDom, document.getElementById('root2'))
理解 React 元素
-
也称
虚拟 DOM
(virtual DOM) 或虚拟节点
(virtual Node) -
它就是一个普通的 JS 对象, 它不是真实 DOM 元素对象
虚拟 DOM: 属性比较少 ==>
较轻
的对象真实 DOM: 属性特别多 ==>
较重
的对象 -
但它有一些自己的特点
虚拟 DOM 可以转换为对应的真实 DOM => ReactDOM.render方法将虚拟DOM转换为真实DOM再插入页面
虚拟 DOM 对象包含了对应的真实 DOM 的关键信息属性
属性 示例 标签名 type: “h1” 标签属性 props: {title: ‘你好, React!’} 子节点 props: {children: ‘Hello React!’}
JSX
基本理解和使用
问题
: React.createElement() 写起来太复杂了
解决
: 推荐使用更加简洁的JSX
JSX 是一种 JS 的扩展语法, 用来快速创建 React 元素(虚拟DOM/虚拟节点)
形式上像 HTML 标签/任意其它标签, 且标签内部是可以套 JS 代码的
const h1 = <h1 className="active">哈哈哈</h1>
浏览器并不认识 JSX 所以需要引入babel将 JSX 编译成 React.createElement 的形式
线上测试: https://www.babeljs.cn/
<!-- 必须引入编译jsx的babel库 -->
<script src="../js/babel.min.js"></script>
<!-- 必须声明type为text/babel, 告诉babel对内部的代码进行jsx的编译 -->
<script type="text/babel">
// 创建React元素 (也称为虚拟DOM 或 虚拟节点)
const vDom = <h1 title="你好, React2!" className="active">Hello React2!</h1>
// 渲染React元素到页面容器div中
ReactDOM.render(vDom, document.getElementById('root'))
</script>
注意:
必须有结束标签
整个只能有一个根标签
空标签可以自闭合
JSX 中使用 JS 表达式
- JSX 中使用 JS 表达式的语法:
{}
- 作用:
指定动态的属性值和标签体文本
- 可以是任意基本类型数据值, 但 null、undefined 和布尔值没有任何显示
- 子元素可以是一个 JS 数组, 但不能是 JS 对象
- style 属性值必须是一个包含样式的 JS 对象
- 可以是 JS 的表达式, 不能是 JS 的语句
- 可以是 react 元素对象
let title = 'I Like You'
const vNode = (
<div>
<h3 name={title}>{title.toUpperCase()}</h3>
<h3>{3}</h3>
<h3>{null}</h3>
<h3>{undefined}</h3>
<h3>{true}</h3>
<h3>{'true'}</h3>
<h3>{React.createElement('div', null, 'atguigu')}</h3>
<h3>{[1, 'abc', 3]}</h3>
<h3 title={title} id="name" className="ative" style={{color: 'red'}}></h3>
{/* <h3>{{a: 1}}</h3> */}
</div>
)
样式处理
行内样式
- 样式属性名使用小驼峰命名法
- 如果样式是数值,可以省略单位
<h2 style={{color: 'red', fontSize: 30}}>React style</h2>
类名
- 必须用 className, 不能用 class
- 推荐, 效率更高些
<h2 className="title">React class</h2>
条件渲染
if…else
let vDom
if (isLoading) {
vDom = <h2>正在加载中...</h2>
} else {
vDom = <div>加载完成啦!</div>
}
ReactDOM.render(vDom, document.getElementById('root'))
三元表达式
const vDom = isLoading ? <h2>正在加载中2...</h2> : <div>加载完成啦2!</div>
ReactDOM.render(vDom, document.getElementById('root'))
&&
const vDom = isLoading && <h2>正在加载中3...</h2>
ReactDOM.render(vDom, document.getElementById('root'))
// 注意: 只适用于只在一种情况下才有界面的情况
列表渲染
- react中可以将数组中的元素依次渲染到页面上
- 可以直接往数组中存储react元素
- 推荐使用数组的 map 方法
- 注意:必须给列表项添加唯一的 key 属性, 推荐使用id作为key, 尽量不要用index作为key
需求: 根据下面的数组显示列表
const courses = [
{id: 1, name: ‘React’},
{id: 3, name: ‘Vue’},
{id: 5, name: ‘小程序’}
]
const courses = [
{id: 1, name: 'React'},
{id: 3, name: 'Vue'},
{id: 5, name: '小程序'}
]
const vDom = (
<div>
<h2>前端框架课程列表</h2>
<ul>
{courses.map(c => <li key={c.id}>{c.name}</li>)}
</ul>
</div>
)
ReactDOM.render(vDom, document.getElementById('root'))
事件处理
绑定事件
React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:
- React 事件的命名采用小驼峰式(camelCase),而不是纯小写。比如:onClick、onFocus 、onMouseEnter
- 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串
const div = <div onClick={事件处理函数}></div>
事件对象
React 根据 W3C 规范来自定义的合成事件, 与原生事件不完全相同
-
处理好了浏览器的兼容性问题
-
阻止事件默认行不能使用 return false, 必须要调用: event.preventDefault()
-
有自己特有的属性, 比如: nativeEvent – 原生事件对象
-
<input>
的 change 监听在输入过程中触发, 而原生是在失去焦点才触发- 原理:内部绑定的是原生 input 事件
function handleClick1(event) {
console.log(event)
alert(event.target.innerHTML)
}
const handleClick2 = (event) => {
const isOdd = Date.now()%2===1
alert(isOdd)
if (!isOdd) {
// return false // 不起作用
event.preventDefault()
}
}
const vDom = <div>
<button onClick={handleClick1}>点击提示按钮文本</button>
<br/>
<br/>
<a href="http://www.baidu.com" onClick={handleClick2}>奇数才去百度</a>
</div>
ReactDOM.render(vDom, document.getElementById('root'))
案例
- 需求:实现评论列表功能
- 如果有评论数据,就展示列表结构 li( 列表渲染 )要包含a标签
- name 表示评论人,渲染 h3
- content 表示评论内容,渲染 p
- 如果没有评论数据,就展示一个 h1 标签,内容为: 暂无评论!
- 用户名的字体25px, 内容的字体20px
- 点击内容区域提示它发表的时间
- 如果有评论数据,就展示列表结构 li( 列表渲染 )要包含a标签
const list = [
{ id: 1, name: 'jack', content: 'rose, you jump i jump', time: '03:21' },
{ id: 2, name: 'rose', content: 'jack, you see you, one day day', time: '03:22' },
{ id: 3, name: 'tom', content: 'jack,。。。。。', time: '03:23' }
]
React的组件
组件允许你将 UI 拆分为独立可复用的代码片段,包括JS/CSS/IMG等。
组件从概念上类似于 JavaScript 函数。它接收参数(即 “props”),内部可以有自己的数据(即 “state”),并返回用于描述页面展示的 React 元素。
一个 React 应用就是由一个个的 React 组件组成的
创建组件的两种方式
函数组件
function App() {
// return null
return <div>App</div>
}
// 函数名就是组件名
ReactDom.render(<App />, document.getElementById('root'))
组件名首字母必须大写. 因为react以此来区分组件元素/标签 和 一般元素/标签
组件内部如果有多个标签,必须使用一个根标签包裹.只能有一个根标签
必须有返回值.返回的内容就是组件呈现的结构, 如果返回值为 null,表示不渲染任何内容
会在组件标签渲染时调用, 但不会产生实例对象, 不能有状态
注意: 后面我们会讲如何在函数组件中定义状态 ==> hooks语法
类组件
import React from "react"
class App extends React.Component {
render () {
return <div>App Component</div>
}
}
ReactDom.render(<App />, document.getElementById('root'))
组件名首字母必须大写.
组件内部如果有多个标签,必须使用一个根标签包裹.只能有一个根标签
类组件应该继承 React.Component 父类,从而可以使用父类中提供的方法或属性
类组件中必须要声明一个render函数, reander返回组件代表组件界面的虚拟DOM元素
会在组件标签渲染时调用, 产生实例对象, 可以有状态
React 脚手架
React 脚手架介绍
React 脚手架是官方提供的 React 开发工具。
脚手架开发项目的特点:
- 更安全
- 包含了 eslint 配置,实时提醒代码异常
- 更方便
- 包含了所有需要的配置(jsx编译、devServer…)
- 下载好了 react 的依赖包(react,react-dom,babel…)
- 搭建好了目录结构
- 更高效
- 实时预览代码运行效果
- 打包优化处理
创建 React 项目
使用 create-react-app 创建项目的流程:
-
下载
npm i create-react-app -g
-
创建项目命令
create-react-app hello-react
-
命令行进入项目文件夹
cd hello-react
-
启动服务
npm start
从 V18 降级到 V17 的版本
最新的脚手架默认使用的是最新的 React18 的版本, 而这个版本是最近才出稳定版, 企业项目还未开始使用
如何降级到 V17 的最新版呢?
-
重新下载 react 和 react-dom, 并指定 17 的版本
npm i react@17 react-dom@17
-
修改入口JS的实现
import React from 'react' import ReactDOM from 'react-dom' import App from './App' ReactDOM.render(<App />, document.getElementById('root'))
类式组件的状态 state
介绍
state (状态)
即数据,是类式组件实例对象的属性,只能在组件内部使用 .
state
属性值为对象, 可以在 state
对象中保存多个数据
作用
state
属性值改变之后能重新渲染组件, 方便更新页面
基本使用
- 初始化 state
- 构造器中: this.state = {xxx: 2}
- 类体中: state = {xxx: 2}
- 读取state数据
- this.state.xxx
- 更新state数据
- 不能直接更新state数据
- 必须
this.setState({ 要修改的属性数据 })
class StateTest extends React.Component {
/* constructor () {
super() // 必须调用super()
// 初始化state
this.state = {
count: 0,
xxx: 'abc'
}
} */
// 初始化状态(简洁语法)
state = {
count: 0,
xxx: 'abc'
}
render () {
// 读取state数据
const {count} = this.state
return <div onClick={() => {
// 直接更新状态数据 => 界面不会自动更新 不可用
// this.state.count = count + 1
// 通过setState()更新state => 界面会自动更新
this.setState({
count: count + 1
})
}}>点击的次数: {count}</div>
}
}
事件回调 this 问题
为了提高代码的性能和阅读性,最好把事件处理函数定义在结构的外面.
但是这样就带来了 this
指向的问题:
问题: 类中定义的事件回调方法中
this
是undefined
, 无法更新state原因: 事件回调都不是组件对象调用的, 都是事件触发后,直接调用的,
class中所有方法都使用严格模式, 所以方法中的this就是undefined
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C3ppXRAc-1658143343435)(images/image-20220520222119252.png)]
-
基础代码:组件的虚拟DOM
<div> <h3>当前count为: 0</h3> <button>点击报错, 有this问题</button><br/> <button>解决办法1 - 包裹箭头函数</button><br/> <button>解决办法2 - bind绑定this </button><br/> <button>解决办法3 - 箭头函数</button> </div>
解决办法1 - 包裹箭头函数
原因: render中的this是组件对象, 处理函数是我们通过组件对象来调用的解决办法2 - bind绑定this
原因: 构造器中的this是组件对象, 将处理函数通过bind绑定为了组件对象解决办法3 - 箭头函数
原理: 利用bind给事件回调绑定this为组件对象(render中的this)
class EventThis extends React.Component {
constructor () { // 构造器中的this是组件对象
super()
/*
this.handle4 = () => {
console.log(this)
this.setState({
count: this.state.count + 3
})
} */
}
// 初始化state
state = { count: 0 }
/*
问题: 类中定义的事件回调方法中this是undefined, 无法更新state
原因: babel编译jsx. 采用的是严格模式, 事件监听函数中this就指向undefined
*/
handle1 () {
console.log(this) // this是undefined
this.setState({ // 报错
count: this.state.count + 1
})
}
handle2 () {
console.log(this)
this.setState({
count: this.state.count + 2
})
}
handle3 () {
console.log(this)
this.setState({
count: this.state.count + 3
})
}
/*
解决办法3 - 箭头函数
原因: 改为箭头函数后, 变为了给组件对象添加属性, 且是在构造器中执行的, 用的就是构造函数中的this
*/
handle4 = () => {
console.log(this)
this.setState({
count: this.state.count + 3
})
}
render () { // render中的this是组件对象
console.log('render()', this.state.count)
return (
<div>
<h3>当前count为: {this.state.count}</h3>
<button onClick={this.handle1}>点击报错, 有this问题</button>
{/*
解决办法1 - 包裹箭头函数
原因: render中的this是组件对象, 处理函数是我们通过组件对象来调用的
*/}
<button onClick={() => {this.handle2()}}>解决办法1 - 包裹箭头函数</button>
<button onClick={this.handle3.bind(this)}>解决办法2 - bind绑定this </button>
<button onClick={this.handle4}>解决办法3 - 箭头函数</button>
</div>
)
}
}
选择:
- 一般用
箭头函数
方式, 编码简洁- 如果要传递特定的数据, 选择用
包裹箭头函数
方式
组件的 props
介绍
props
是 『properties』 的缩写, 也是类式组件实例对象的属性
作用
props
用来接收组件外部数据,实现组件的解耦与复用
基本使用
<Person name="练习生" age="28" gender="男" />
<Person {...kun} />
props 的特点
- 可以给组件传递任意类型的数据
- props 是只读的对象,只能读取属性的值,不要修改props
- 可以通过
...
运算符来将对象的多个属性分别传入子组件 - 如果父组件传入的是动态的 state 数据, 那一旦父组件更新 state 数据, 子组件也会更新
-
子组件
// 函数组件 export function FunProps(props) { return <h2>FunProps-个人信息: 姓名: {props.name}, 年龄: {props.age}</h2> } // 类组件 export class ClassProps extends React.Component { render () { const { myName, age} = this.props return <h2>ClassProps-个人信息: 姓名: {myName}, 年龄: {age}</h2> } }
-
父组件
class App extends React.Component { state = { person: { myName: 'tom', age: 12 } } render () { const {myName, age} = this.state.person return <div> <p>人员信息: {myName + ' : ' +age}</p> <button onClick={() => { this.setState({ person: { myName: myName+'--', age: age+1} }) }}>更新人员信息</button> <br/> <FunProps name={myName} age={age}/> <hr/> {/* <ClassProps myName={myName} age={age}/> */} <ClassProps {...this.state.person}/> </div> } }
props 校验 (了解)
对于组件来说,props 是外来的,无法保证组件使用者传入什么格式的数据
如果传入的数据格式不对,可能会导致组件内部报错
关键问题:组件的使用者不知道明确的错误原因
允许在创建组件的时候,就指定 props 的类型、格式等
作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性
实现方式:
-
导入 prop-types 包
-
使用propTypes来给组件的props添加校验规则
// 常见类型:string、bool、number、object、array、func还有element(react元素)
// 必填项:isRequired
// 特定结构的对象:shape({ })
import React from 'react'
import PropTypes from 'prop-types'
export default class PropCheck extends Component {
// 指定属性名/属性值类型/属性的必要性
static propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
likes: PropTypes.array,
}
// 指定属性的默认值
static defaultProps = {
likes: ['A', 'B', 'C']
}
render () {
const { name, age, likes} = this.props
return <div>
<h2>PropCheck-个人信息</h2>
<p>姓名: {name}</p>
<p>年龄: {age}</p>
<p>爱好: {likes.join('-')}</p>
</div>
}
}
// 函数组件只能这么指定
// FunctionProps.propTypes = {}
// FunctionProps.defaultTypes = {}
<ClassProps myName={myName} age={age} />
props 默认值
作用:给 props 设置默认值,在未传入 props 时生效
实现方式:
function App(props) {
return (
<div>
此处展示props的默认值:{props.pageSize}
</div>
)
}
// 设置默认值
App.defaultProps = {
pageSize: 10
}
// 不传入pageSize属性
<App />
收集表单数据
非受控组件
表单项不与state数据相向关联, 需要手动读取表单元素的值
借助于 ref,使用原生 DOM 方式来获取表单元素值
ref 的作用:获取 DOM 或组件对象
<form>
<h2>登陆页面</h2>
用户名: <input type="text"/> <br/>
密 码: <input type="password"/> <br/>
<input type="submit" value="登 陆"/>
</form>
import React, { Component } from 'react'
/*
非受控组件:
表单项不与 state 数据相向关联, 需要手动读取表单元素的值
编码过程
1. 初始化创建 ref 容器并保存到组件对象上
2. 将 ref 容器通过 ref 属性交给表单项标签 => 渲染时内部会将对应的真实DOM保存到 ref 容器的 current属性上
3. 点击提交按钮时, 通过 ref 容器的 current 属性得到 input DOM 元素 => 就可以读取其 value了
不足
不够自动化/无法进行实时数据校验
*/
export default class FormTest extends Component {
// 1. 初始化创建ref容器并保存到组件对象上
nameRef = React.createRef()
ageRef = React.createRef()
submit = (e) => {
// 阻止事件的默认行为(提交表单)
e.preventDefault()
// 3. 点击提交按钮时, 通过ref容器的current属性得到input DOM元素 => 就可以读取其value了
console.log(this.nameRef.current)
const name = this.nameRef.current.value
const age = this.ageRef.current.value
alert(`提交登陆的ajax请求 name=${name}, age=${age}`)
}
render() {
return (
<form action=''>
<h2>登陆页面(非受控组件)</h2>
{/* 2. 将ref容器通过ref属性交给表单项标签 => 渲染时内部会将对应的真实DOM保存到ref容器的current属性上 */}
用户名: <input type="text" ref={this.nameRef}/> <br/>
密 码: <input type="password" ref={this.ageRef}/> <br/>
<input type="submit" value="登 陆" onClick={this.submit}/>
</form>
)
}
}
受控组件
组件中的表单项根据状态数据动态初始显示和更新显示, 当用户输入时实时同步到状态数据中
也就是实现了页面表单项与 state 数据的双向绑定
实现方式
- 在 state 中添加一个状态,作为表单元素的value值(控制表单元素值的来源)
- 给表单元素绑定 change 事件,将 表单元素的值 设置为 state 的值(控制表单元素值的变化)
/*
受控组件
*/
export class FormTest2 extends React.Component {
state = {
name: 'admin',
pwd: '123456'
}
submit = (e) => {
// 阻止事件的默认行为(提交表单)
e.preventDefault()
const {name, pwd} = this.state
alert(`提交登陆请求 name=${name}, pwd=${pwd}`)
}
handleNameChange = (e) => {
this.setState({
name: e.target.value
})
}
handlePwdChange = (e) => {
this.setState({
pwd: e.target.value
})
}
render () {
const {name, pwd} = this.state
return (
<form action=''>
<h2>登陆页面(受控组件)</h2>
用户名: <input type="text" value={name} onChange={this.handleNameChange}/> <br/>
密 码: <input type="password" value={pwd} onChange={this.handlePwdChange}/> <br/>
<input type="submit" value="登 陆" onClick={this.submit}/>
</form>
)
}
}
//补充: 文本框、文本域、下拉框 操作value属性 复选框 操作checked属性
优化1: 使用同一个事件函数处理
问题: 2个input的onChange事件处理函数代码重复
解决: 使用一个事件函数
/*
受控组件=> 优化
*/
export class FormTest3 extends React.Component {
state = {
name: 'admin',
pwd: '123456'
}
submit = (e) => {
// 阻止事件的默认行为(提交表单)
e.preventDefault()
const {name, pwd} = this.state
alert(`提交登陆请求 name=${name}, pwd=${pwd}`)
}
handleChange = (e, prop) => {
this.setState({
[prop]: e.target.value
})
}
render () {
const {name, pwd} = this.state
return (
<form action=''>
<h2>登陆页面(优化)</h2>
用户名: <input type="text" value={name}
onChange={e => this.handleChange(e, 'name')}/> <br/>
密 码: <input type="password" value={pwd}
onChange={e => this.handleChange(e, 'pwd')}/> <br/>
<input type="submit" value="登 陆" onClick={this.submit}/>
</form>
)
}
}
优化2: 利用高阶函数
/*
受控组件=> 优化2
*/
export class FormTest4 extends React.Component {
state = {
name: 'admin',
pwd: '123456'
}
submit = (e) => {
// 阻止事件的默认行为(提交表单)
e.preventDefault()
const {name, pwd} = this.state
alert(`提交登陆请求 name=${name}, pwd=${pwd}`)
}
handleChange = (prop) => {
return (e) => {
this.setState({
[prop]: e.target.value
})
}
}
render () {
const {name, pwd} = this.state
return (
<form action=''>
<h2>登陆页面(优化2)</h2>
用户名: <input type="text" value={name} onChange={this.handleChange('name')}/> <br/>
密 码: <input type="password" value={pwd} onChange={this.handleChange('pwd')}/> <br/>
<input type="submit" value="登 陆" onClick={this.submit}/>
</form>
)
}
}
组件的生命周期
生命周期图谱: https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
组件的生命周期三个大阶段
挂载阶段
流程: constructor ==> render ==> componentDidMount
触发: ReactDOM.render(): 渲染组件元素
constructor: 创建组件时,最先执行 . 一般用于: 1. 初始化state 2. 为事件处理程序绑定this
render: 每次组件渲染都会触发 注意: 不能在render中调用setState()
componentDidMount: 组件挂载(完成DOM)渲染后 一般用于: 1. 发送网络请求 2. DOM操作 (只要是组件以上来就要做的事情,都应该写在挂载成功的回调中)
更新阶段
流程: render ==> componentDidUpdate
触发: setState() , forceUpdate(), 组件接收到新的props
componentDidUpdate: 组件更新(完成DOM渲染)后
卸载阶段
流程: componentWillUnmount
触发: 不再渲染组件
**componentWillUnmount:**组件卸载(从页面中消失) 执行清理操作
旧版react生命周期(了解)
componentWillMount
componentWillUpdate,
componentWillReceiveProps
以上生命周期钩子函数在 React v16.3 后废弃
Hook
- Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性
- Hook 也叫钩子,
本质就是函数
,能让你在函数式组件中使用状态
和生命周期函数
等功能 - Hook 语法 基本已经代替了类组件的语法,后面的 React 项目就完全是用 Hook 语法了
useState()
用来定义状态数据,可以多次调用, 产生多个状态数据
import { useState } from 'react'
export default function App() {
console.log('App')
/*
1. 声明一个state数据变量, 名称任意 xxx
2. 声明一个更新state数据的函数, 名称一般: setXxx
3. useState参数指定初始值
4. 后面更新调用时返回内部存储的最新state数据
*/
const [count, setCount] = useState(0)
return <div>
<p>点击次数: { count }</p>
<button onClick={() => setCount(count + 1)}>点击增加一次</button>
<button onClick={() => setCount(count => count + 2)}>点击增加二次</button>
</div>
}
useEffect()
可以在一个组件中多次使用
相当于
componentDidMount,componentDidUpdate 和 componentWillUnmount
的组合
import {useState, useEffect} from 'react'
export default function Test(props) {
const [msg, setMsg] = useState('--')
const [count, setCount] = useState(1)
/* 相当于 componentDidMount */
useEffect(() => {
setMsg(localStorage.getItem('msg_key') || '')
}, [])
/* 相当于 componentDidMount + msg更新后的componentDidUpdate */
useEffect(() => {
localStorage.setItem('msg_key', msg)
}, [msg])
/* 相当于 componentDidMount */
useEffect(() => {
const id = setInterval(() => {
console.log('========')
/* setCount(count + 1)
document.title = count + 1 */
setCount(count => {
count += 1
document.title = count
return count
})
}, 1000)
/* 相当于 componentWillUnmount */
return () => {
clearInterval(id)
}
}, [])
return (
<div>
<h3>state.count: {count}</h3>
<h3>state.msg: {msg}</h3>
<button onClick={() => setMsg(msg + '-')}>更新msg</button>
</div>
)
}
Hook 注意:
只在最顶层使用 Hook,不要在条件或循环中
只在React组件函数内部中调用 Hook, 不要在组件函数外部调用
案例- TODO List 案例
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UghCVKBI-1658143343438)(images/image-20220523075242606.png)]
功能描述
- 动态显示初始列表
- 添加一个 todo
- 删除一个 todo
- 反选一个 todo
- todo 的全部数量和完成数量
- 全选/全不选 todo
- 删除完成的 todo
AJAX
-
在 react 项目开发中,基本都是使用 axios 库来发送 AJAX 请求与后台通信
-
而发 AJAX 请求的时机分下面三种情况
-
初始化发一次请求: 在
componentDidMount
中发送 -
在
事件回调
函中发送请求 -
在某个 state 或 props 数据发生改变后发送请求: 在
componentDidUpdate
中发送
-
-
接口1: https://api.github.com/search/repositories?q=re&sort=stars
-
接口2: https://api.github.com/search/users?q=a
测试案例一
import React from "react"
import axios from 'axios'
class App extends React.Component {
state = {
url: '',
repoName: ''
}
async componentDidMount () {
const response = await axios('https://api.github.com/search/repositories?q=re&sort=stars')
const {name, html_url} = response.data.items[0]
this.setState({
url: html_url,
repoName: name
})
}
render () {
const { url, repoName } = this.state
if (!url) {
return <h3>正在加载中...</h3>
}
return <div>
最受欢迎的仓库是: <a href={url}>{repoName}</a>
</div>
}
}
测试案例二
搭建后台接口
- server.js
- 使用 node + express + axios
/*
后台服务器应用模块: 使用express快速搭建后台路由
*/
const express = require('express')
const axios = require('axios')
const app = express()
// 能解析urlencode格式的post请求体参数
app.use(express.urlencoded())
// 能解析json格式的请求体参数
app.use(express.json())
// 根路径路由
app.get('/', (req, res) => {
res.send({status: 1, data: '我是测试数据'})
})
// 搜索用户的路由
app.get('/search/users', (req, res) => {
console.log('host', req.headers['host'])
const q = req.query.q
axios.get('https://api.github.com/search/users', {
params: {q}
})
.then(response => {
const result = response.data
res.send(result)
}).catch(error => {
console.log(error.message)
})
})
// 启动监听服务
app.listen('4000', () => {
console.log('server listen on http://localhost:4000')
})
react脚手架配置代理
在package.json中追加如下配置
"proxy":"http://localhost:4000"
说明:
- 优点:配置简单,前端请求资源时可以不加任何前缀。
- 缺点:不能配置多个代理。=>
后面项目中我们会讲解配置多个代理的方式
- 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
组件通讯
react组件通讯有三种方式.分别是props, context, pubsub
props
单向数据流: 非函数属性通过标签属性, 由外层组件逐级传递给内层组件
父子间通信
祖孙间通信
兄弟间通信
context
与任意后代直接通信
一般应用中不使用, 但一些插件库内部会使用context封装, 如: react-redux
-
调用 React. createContext() 创建 context 对象
const context = React.createContext()
-
在
外部组件
中使用 context 上的 Provider 组件作为父节点, 使用value属性定义要传递的值<context.Provider value={要传递的值}> 提供数据 <div className="App"> <Child1 /> </div> </context.Provider>
-
在
任意后代组件
中, 读取Provider 组件提供的数据方式一:
function Child () { return <div> <context.Consumer> 消费者 { data => <p>{ data }</p> } </context.Consumer> </div> }
方式二:
class Child extends Component { // 将context对象保存到contextType静态属性上 // => 内部会自动将数据保存到组件对象的context属性上 static contextType = context render() { // 通过context属性得到Provider提供的数据 return <div>{this.context}</div> } }
**实现二: **
export default class Demo extends Component {
// 给要使用的Context的Demo类,添加静态contextType属性, 并赋值为context对象
// 那么在Demo的实例对象上context属性中就可以获取到需要的值
static contextType = context
render() {
return <div>{this.context}</div>
}
}
应用: 利用 context 改造 todos 案例
pubsub
兄弟/任意组件间直接通信
发布订阅机制: publish / subscribe
pubsub-js是一个用JS编写的库。
利用订阅发布模式, 当一个组件的状态发生了变化,可以通知其他任意组件更新这些变化
实现:
-
安装
npm install pubsub-js / yarn add pubsub-js
-
导入
import PubSub from "pubsub-js" // 导入的PubSub是一个对象.提供了发布/订阅的功能
-
pubsub-js 提供的方法
// 订阅消息 // 参数一: 消息名 // 参数二: 用于接收数据的函数 // token 订阅消息返回的令牌(用于取消订阅) const token = PubSub.subscribe('消息名', function (msg, data) { console.log( msg, data ); }); // 发布消息 // 参数一: 消息名 // 参数二: 要传递的数据 PubSub.publish('消息名', 'hello world!'); // 取消指定的订阅 PubSub.unsubscribe(token); // 取消某个话题的所有订阅 PubSub.unsubscribe(消息名); /* div.addEventListener('click', (event) => {}) 我们点击div => 浏览器自动帮我分发事件: 事件名, 包含事件相关数据的事件对象 div.removeEventListener('click') */
利用 pubsub 改造 users 案例
Fragment
doucmentFragment: 是原生DOM中, 内存中可以用来保存多个DOM节点对象的容器
如果将这个fragment添加到页面中, 它本身不会进入页面, 它的所有子节点会进行页面
react组件中只能有一个根组件.
之前使用div包裹的方式会给html结构增加很多无用的层级
为了解决这个问题,可以使用React.Fragment
测试DocumentFragment
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试DocumentFragment</title>
</head>
<body>
<div id="test"></div>
<script>
const testDiv = document.getElementById('test')
const h1 = document.createElement('h1')
h1.innerHTML = '我是标题'
const p = document.createElement('p')
p.innerHTML = '我是内容'
const fragment = document.createDocumentFragment()
fragment.appendChild(h1)
fragment.appendChild(p)
testDiv.appendChild(fragment)
</script>
</body>
</html>
不使用React.Fragment
function Hello(){
return (
// 渲染到页面之后,这个div就是一个多余的
<div>
<h1>fragment</h1>
<p>hello react</p>
</div>
)
}
使用React.Fragment
function Hello(){
return (
// 这样就只会渲染h1和p
<React.Fragment>
<h1>fragment</h1>
<p>hello react</p>
</React.Fragment>
)
}
使用简写(空标签 <>)
function Hello(){
return (
// 这是React.Fragment的简写形式
<>
<h1>fragment</h1>
<p>hello react</p>
</>
)
}
其他 hooks
useContext
在hook组件函数中读取context的Provide提供的数数据
function Child() {
const data = useContext(context)
return (
<div>{data}</div>
)
}
官方 Hooks
利用 Hooks 语法改造 todos 和 users 案例
function Hello(){
return (
// 这样就只会渲染h1和p
<React.Fragment>
<h1>fragment</h1>
<p>hello react</p>
</React.Fragment>
)
}
使用简写(空标签 <>)
function Hello(){
return (
// 这是React.Fragment的简写形式
<>
<h1>fragment</h1>
<p>hello react</p>
</>
)
}
其他 hooks
useContext
在hook组件函数中读取context的Provide提供的数数据
function Child() {
const data = useContext(context)
return (
<div>{data}</div>
)
}
官方 Hooks
利用 Hooks 语法改造 todos 和 users 案例
吐槽:csdn居然不支持jsx。。。