官网:https://zh-hans.reactjs.org/
用于构建用户界面(UI —HTML页面)的 JavaScript 库
MVC角度来看,React仅仅是视图层的解决方案,只负责视图的渲染
react+reactDom /react-router/redux 框架
React起源于Facebook内部项目, Instragram网站,2013年5月开源React
最流行的前端开发框架之一 框架对比https://www.npmtrends.com/
框架:提供了一整套完整的解决方案
库: 一系列函数的集合
声明式
只需要描述UI看起来是什么样式,就跟写HTML一样,只负责渲染UI
基于组件
组件是React最重要的内容
学习一次,随处可用
可以开发web应用,开发移动端,还可以开发VR应用
npm i react react-dom
引入react和react-dom
创建元素
// 创建元素节点
// console.log(React)
/*
创建元素节点
1:元素名称
2:元素属性 传递的是个对象
3:元素内容
*/
// let title = React.createElement('li', null, 'hello React')
// console.log(title)
let title = React.createElement(
'p',
{ title: '我是段落', id: 'p1' },
React.createElement('span', null, '我是span标记')
)
渲染到页面
// 渲染到页面上
ReactDOM.render(title, document.getElementById('app'))
零配置,无需手动配置繁琐的工具即可使用,只关注业务
全局安装create-react-app
npm i create-react-app -g
创建项目
create-react-app myreact(项目名称)
如果不想全局安装,可以使用npx
npx create-react-app my-pro
npx目的:提升包内提供的命令行工具的使用体验
之前:先安装脚手架工具,再使用包中提供的命令搭建项目
使用npx ,无需安装脚手架工具,就可以直接使用这个包提供的命令
create-react-app 是脚手架名称 不能随意更改
my-pro 是项目名称
注意
npx create-react-app my-pro这个过程会安装四个东西
启动项目
npm start
README.md 使用方法的文档
node_modules 所有依赖安装的目录
package-lock.json 锁定安装时的包的版本号,保证团队的依赖能保持一致
public 公共静态资源目录
src 开发用的源代码目录
// 从react包中引入React,引入该包就可以使用react的jsx语法
import React from 'react'
// 把自己写的react组件渲染到页面上,讲=将虚拟dom转换成真实的DOM
import ReactDOM from 'react-dom/client'
// 公共样式
import './index.css'
// 根组件
import App from './App'
// pwa渐进式开发相关的包,和react本身没有什么关系,可以不引入
import reportWebVitals from './reportWebVitals'
// 指定了将我们的react组件渲染到哪里
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
// 这种在js中直接写html标签的语法叫做jsx语法,是react特有的属性
)
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
// pwa相关的
reportWebVitals()
index.js 入口文件
// 从react包中引入React,引入该包就可以使用react的jsx语法
import React from 'react'
// 把自己写的react组件渲染到页面上,讲=将虚拟dom转换成真实的DOM
import ReactDOM from 'react-dom/client'
// 公共样式
import './index.css'
// 根组件
import App from './App'
// 指定了将我们的react组件渲染到哪里
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
// 这种在js中直接写html标签的语法叫做jsx语法,是react特有的属性
)
注意:
代表的是使用严格模式渲染组件,在严格模式下可以帮助我们
- 识别不安全的生命周期
- 关于使用过的字符串ref API的警告
- 检测额外的副作用
- 检测过时的context API
createElement创建元素,代码繁琐,结构不直观
React 为方便 View 层组件化,承载了构建 HTML 结构化页面的职责,即提供了JSX语法糖。JSX 将
XHTML 语法直接加入到 JavaScript 代码中,再通过翻译器转换到纯 JavaScript 后由浏览器执行。
在实际开发中,JSX 在产品打包阶段都已经编译成纯 JavaScript,不会带来任何副作用,反而会让
代码更加直观并易于维护。
JSX是JavaScript XML的简写,表示在javaScript代码中可以直接写HTML格式的代码
好处:声明式语法更加直观,与HTML结构相同,学习成本低,效率高
jsx语法要求最外层只能有一个根节点
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
欢迎学习React!!!
React是专注于视图层的JavaScript库
)
如果真要想将两个并列的标签放在一个根节点中还不想产生额外的标签结构,可以使用
React.Fragment 标签来包裹元素
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
欢迎学习React!!!
React是专注于视图层的JavaScript库
React是目前最火的前端开发框架之一
)
jsx语法实际上是js+xhtml的组合,因此要求单标签必须要闭合
在jsx中要求 标签必须要有 alt 属性,否则会有警告
在jsx语法中为了防止和js相关关键字冲突,要求 class 必须要写成 className ,label标签中的 for 属性必须要写成 htmlFor
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
欢迎学习React!!!
React是专注于视图层的JavaScript库
)
JSX语法的原理实际上就是将我们的html结构用纯JavaScript对象来表示,看下面代码如何用纯
JavaScript对象来描述
欢迎学习React!!!
React是专注于视图层的JavaScript库
将以上html代码转成JavaScript对象就是这个样子:
{
"type": "div",
"props": {
"id": "app",
"className": "app-root"
},
"children": [
{
"type": "h1",
"props": {
"className": "title"
},
"children": ["欢迎学习React!!!"]
},
{
"type": "p",
"props": null,
"children": ["React是专注于视图层的JavaScript库"]
}
]
}
但是如果我们这么表示一个html结构的话写起来太不方便了,而且结构看起来也不清晰
React.js中扩展了JavaScript语法的功能,让html标签可以直接写在JavaScript中,然后React会自
动帮我们将html标签转成纯JS对象。这样使用者写起来就方便多了,而且结构也更加的清晰了
用户可以直接这样写
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
欢迎学习React!!!
React是专注于视图层的JavaScript库
)
React.js会使用@babel/preset-react 插件将JSX语法编译为 createElement() 方法
root.render(
React.createElement(
"div",
{
"id": "app",
"className": "app-root"
},
React.createElement(
"h1",
{
"className": "title"
},
"欢迎学习React!!!"
),
React.createElement(
"p",
null,
"React是专注于视图层的JavaScript库"
)
)
);
然后React会将createElement创建的对象转化成React元素(纯js对象)
{
"type": "div",
"props": {
"id": "app",
"className": "app-root"
},
"children": [
{
"type": "h1",
"props": {
"className": "title"
},
"children": ["欢迎学习React!!!"]
},
{
"type": "p",
"props": null,
"children": ["React是专注于视图层的JavaScript库"]
}
]
}
总结:
JSX语法的原理其实就是将我们写的 html标签 通过 @babel/preset-react 插件编译成
createElement 方法,然后在转成 React元素 (纯js对象)
在React中用{} 来渲染属性或者变量, 这个{} 就相当于js的一个执行环境
let msg = 'hello 大家好'
let title = 'react专注于视图'
root.render(
{/*将要渲染的属性或者变量放在单花括号中 */}
{msg}
{/* 属性渲染 要渲染的属性写在 {}内就可以了 */}
React专注于视图层
)
React中的条件渲染主要是通过三目运算符或者短路运算符来完成,单括号中可以写表达式
let flag = false
root.render(
{/* 使用三目运算符 */}
{flag ? (
我只能满足flag为true的条件时才能被渲染
) : (
我只能满足flag为false的条件时才能被渲染
)}
{/* 使用短路 */}
{false && 通过短路运算符来进行渲染}
)
React中循环渲染主要是通过数组的map方法来完成的
let arr = [
{ id: 1, uname: 'aaaa' },
{ id: 2, uname: 'bbbb' },
{ id: 3, uname: 'ccccc' },
]
root.render(
{/* 数组的map方法 返回一个html标签组成的数组
key 唯一标识
*/}
{arr.map((item) => (
- {item.uname}
))}
)
出于安全考虑,React中所有表达式的内容会被转义,如果直接输入,标签会被当做文本,为了防止xss攻击,如果要真的转成HTML标签 我们需要借助于dangerouslySetInnerHTML
const ele = '我是strong标签'
root.render(
{ele}
)
以上的写法html标签并没有被解析,如果我们希望被解析,我们这样做
const ele = '我是strong标签'
root.render(
{ __html: ele }}>
)
React创建组件的方式使用类的继承, ES6 class是目前官方推荐的方式,使用了ES6标准语法来构建
// 从React包中引入Component类,我们自己创建的所有组件都要继承Component类
import { Component } from 'react'
// 创建一个Child组件 继承Component
class Child extends Component {
render() {
return Child组件
}
}
// 导出组件
export default Child
注意点:
我们创建的组件必须要继承React.Component这个父类
必须要有render方法
render方法中必须要返回React元素(JSX)
在index.js中引入组件
import React from 'react'
import ReactDOM from 'react-dom/client'
import Child from './Child'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
)
注意:组件名的首字母必须大写,为了和普通的html标签区分开
函数组件又叫做无状态的组件,定义一个函数组件就是定义一个函数,建议使用箭头函数
import Child from './Child.js'
// 创建一个函数组件
const App = (props) => (
欢迎大家学习React
)
// 导出组件
export default App
注意点:
函数的首字母必须要大写,为了区分是一个组件不是一个普通函数
函数中必须要返回react元素(JSX)
给虚拟dom添加行内样式,使用表达式传入样式对象的方式来实现
React推荐我们使用行内样式,因为React觉得每一个组件都是一个独立的整体
{/* 外面的{} 表示我们要在jsx里插入js 里面的是对象的 {} */}
{ color: '#c00', fontSize: '14px' }}>欢迎大家学习React
把css样式写在一个单独的css文件里,通过import导入
//App.css
.tit {
background-color: yellow;
}
App.js中导入
import './App.css'
const App = (props) => (
123
)
React事件绑定采用的是 on+事件名的方法
事件名的首字母要大写
React中的事件并不是原生的事件,而是合成事件,可以理解为React是对JavaScript的原生事件进行了封装
class Child extends Component {
render() {
return (
)
}
}
直接在render里写行内的箭头函数,逻辑不复杂适用,事件处理函数中this指向当前对象
class Child extends Component {
msg = 'hello world'
render() {
return (
{/* this指向当前对象 this.msg 打印出 hello world */}
)
}
}
在组件里定义一个非箭头函数,然后在render里直接使用,事件处理函数中this指向undefined,需要使用bind方法来改变this指向
class Child extends Component {
msg = 'hello world'
render() {
return (
{/* 事件处理函数第二种 */}
)
}
// 定义事件处理函数
handleClick() {
console.log(this.msg)
}
}
没有bind改变this指向时 ,this为undefined的原因:
这种写法相当于把handleClick这个类方法直接赋值给了button这个Dom元素的onClick属性,onClick这个方法是由window直接调用的,所以this本来应该是window,但是在React组件中的作用域是局部作用域并且开启了严格模式,严格模式下全局作用域函数中的this是undefined
在组件内使用箭头函数定义一个方法,事件处理函数中this指向当前对象,但是不能事件传参
class Child extends Component {
msg = 'hello world'
render() {
return (
{/* 事件处理函数第3种 */}
)
}
// 定义事件处理函数
handleClick = () => {
console.log(this)
}
}
直接在render函数里使用箭头函数定义一个事件处理函数,然后在事件处理函数中调用在组件内定义的方法(推荐)
class Child extends Component {
msg = 'hello world'
render() {
return (
{/* 事件处理函数第3种 */}
)
}
// 定义事件处理函数
handleClick = () => {
console.log(this)
}
}
和普通浏览器一样,事件处理函数会自动传入一个event对象,这个对象和普通的浏览器event对象所包含的属性和方法都基本一致,不同的是React中的event对象并不是浏览器提供的,而是它自己内部所构建的,它同样具有event.stopPropagation,event.preventDefault
import { Component } from 'react'
class App extends Component {
render() {
return (
)
}
// 定义事件处理函数
handleClick(ev) {
console.log(ev) //ev react内部构建的事件对象
console.log(ev.nativeEvent) //获取的原生的事件对象
}
}
export default App
import { Component } from 'react'
class App extends Component {
arr = ['aaa', 'bbb', 'ccc']
render() {
return (
{/* 多个参数 */}
{this.arr.map((item, index) => (
- {
this.handleClicks(ev, index)
}}
>
{item}
))}
)
}
// 事件处理函数的第一个参数是Event对象,其余参数依次向后排即可
handleClicks(ev, idx) {
console.log(ev) //获取react内部所构建的事件对象
console.log(idx)
}
}
export default App
ref就是给我们的标签或者组件起名字的,通过ref属性 我们可以获取到当前的标签或者组件
给标签设置ref属性
只有在非严格模式下才可以使用
import { Component } from 'react'
class App extends Component {
arr = ['aaa', 'bbb', 'ccc']
render() {
return (
{/* 通过ref属性给标签起名字 */}
欢迎学习React
)
}
// 定义事件处理函数
handleClick() {
// 通过this.refs.名字来获取dom元素,这种写法只有在非严格模式下才可以使用 refs 弃用
console.log(this.refs.uname)
}
}
export default App
新的写法
import { Component, createRef } from 'react'
import Child from './Child'
class App extends Component {
// 通过createRef方法创建一个ref实例对象
uname = createRef()
render() {
return (
{/* 通过ref属性给标签起名字 */}
{/* 欢迎学习React
*/}
)
}
// 定义事件处理函数
handleClick() {
// 通过this.uname.current方法来获取
console.log(this.uname.current) //打印出p标签这个dom元素
// 若是组件 获取到的是组件实例对象
}
}
export default App
状态(state)就是React中挂载数据的地方,由React组件内部自己维护,它是私有的,通过state可以实现数据的响应式
获取state的值通过 this.state.值的方法
定义state
第一种
import { Component } from 'react'
class App extends Component {
// 定义state(简化写法)
state = {
msg: 'hello React',
arr: ['aaa', 'bbb', 'ccc'],
}
render() {
return (
{/* 获取state中的值 */}
{this.state.msg}
{this.state.arr}
)
}
}
export default App
第二种
import { Component } from 'react'
class App extends Component {
// 构造器
constructor(props) {
super(props)
// 定义state
this.state = {
msg: 'hello React',
arr: ['aaa', 'bbb'],
}
}
render() {
return (
{/* 获取state中的值 */}
{this.state.msg}
{this.state.arr}
)
}
}
export default App
修改state
在React中通过this.state直接修改数据的话,react是无法得知的,所以需要使用特殊的修改状态的方法setState来修改state的状态
import { Component } from 'react'
class App extends Component {
// 构造器
constructor(props) {
super(props)
// 定义state
this.state = {
msg: 'hello React',
arr: ['aaa', 'bbb'],
}
}
render() {
return (
{/* 获取state中的值 */}
{this.state.msg}
{this.state.arr}
)
}
handleClick = () => {
// this.state.msg = 'hello nihao' 错误的使用
// 使用setState方法来修改state
this.setState({
msg: '欢迎来学习React',
arr: ['111'],
})
}
}
export default App
setState是异步的,这样可以将多次操作合并成一次操作,节省性能
import { Component } from 'react'
class App extends Component {
// 构造器
constructor(props) {
super(props)
// 定义state
this.state = {
msg: 'hello React',
arr: ['aaa', 'bbb'],
num: 0,
}
}
render() {
return (
{/* 获取state中的值 */}
{this.state.num}
)
}
handleClick = () => {
// 点击的时候多次调用setState方法
this.setState({
num: this.state.num + 1,
})
this.setState({
num: this.state.num + 1,
})
this.setState({
num: this.state.num + 1,
})
// 第一次点击网页显示结果为 0
// 结果看出 React将三次setState操作合并成了一次操作,而且是先执行的console.log
// 再执行setState
console.log(this.state.num)
}
}
export default App
setState有两个参数
第一个参数:对象或者是函数
import { Component } from 'react'
class App extends Component {
// 构造器
constructor(props) {
super(props)
// 定义state
this.state = {
num: 0,
}
}
render() {
return (
{/* 获取state中的值 */}
{this.state.num}
)
}
handleClick = () => {
// 点击的时候多次调用setState方法
this.setState((prevState, props) => {
// prevState 上一次的state的值 --对象
// props 组件传递的值
return {
num: prevState.num + 1,
}
})
this.setState((prevState, props) => {
return {
num: prevState.num + 1,
}
})
this.setState((prevState, props) => {
return {
num: prevState.num + 1,
}
})
// setState第一个参数是函数的话,我们可以得到上一次state更新后的值
}
}
export default App
第二个参数:回调函数,用于得到setState修改完后的state的值,是可选的
import { Component } from 'react'
class App extends Component {
// 构造器
constructor(props) {
super(props)
// 定义state
this.state = {
num: 0,
msg: 'hello',
}
}
render() {
return (
{/* 获取state中的值 */}
{this.state.num}
)
}
handleClick = () => {
// 点击的时候多次调用setState方法
this.setState(
(prevState, props) => {
// prevState 上一次的state的值 --对象
// props 组件传递的值
return {
num: prevState.num + 1,
}
},
() => {
//在回调函数内部 可以得到使用setState方法更新后的state的值,在回调函数外部是无法得到
console.log(this.state.num, '回调函数') //1 最新的值
}
)
console.log(this.state.num) //0
// setState第一个参数是函数的话,我们可以得到上一次state更新后的值
}
}
export default App
不要直接修改state中的数据后再通过setState进行赋值,正确的做法是将原有的数据复制一份
import { Component } from 'react'
class App extends Component {
state = {
arr: ['HTML', 'CSS', 'JavaScript', 'Vue'],
}
render() {
return (
{/* 获取state中的值 */}
{this.state.arr.map((item, index) => (
- {item}
))}
)
}
handleClick = () => {
// 错误的写法
// 直接修改state中的数据 再使用setState进行赋值,React中是不建议直接修改state中的数据的
// this.state.arr.push('算术')
// this.setState({
// arr: this.state.arr,
// })
// 正确的写法
// 先将state中要修改的数据复制一份
const copyArr = [...this.state.arr]
copyArr.push('算术') //操作复制出来的数组
this.setState({
arr: copyArr, //将复制出来的数组赋值给arr
})
}
}
export default App
setState响应式原理:一旦执行了setState方法,就会触发render方法,包括子组件的render方法
import { Component } from 'react'
// 子组件
class Son extends Component {
render() {
console.log('子组件的render被执行了')
return 我是子组件
}
}
// 父组件
class App extends Component {
state = {
msg: 'hello',
}
render() {
console.log('F组件的render被执行了')
return (
)
}
handleClick = () => {
this.setState({
msg: '123',
})
}
}
export default App
https://www.crx4chrome.com/crx/3068/
概念:用state来绑定表单元素的数据,是React的state成为表单元素的唯一数据源,这种既控制React组件渲染又控制着用户输入的过程的表单元素叫做受控组件
受控组件的核心
import { Component } from 'react'
class App extends Component {
state = {
username: '123',
desc: '',
city: '3',
sex: '男',
checked: false,
}
render() {
return (
{/* state的值和input的value属性绑定
给input绑定onChange事件
*/}
用户名:
描述:
城市:
性别: 男
女
{this.state.sex}
)
}
changeUsername = (ev) => {
// setState修改state的值
this.setState({
// 获取用户输入的值 赋值给username
username: ev.target.value,
})
}
changeDesc = (ev) => {
this.setState({
desc: ev.target.value,
})
}
changeCity = (ev) => {
this.setState({
city: ev.target.value,
})
}
changeSex = (ev) => {
this.setState({
sex: ev.target.value,
})
}
// 与input元素不同的是复选框绑定的是checked属性
changeChecked = (ev) => {
this.setState({
checked: ev.target.checked,
})
}
}
export default App
import { Component } from 'react'
class App extends Component {
state = {
username: '123',
desc: '',
city: '3',
sex: '男',
checked: false,
}
render() {
return (
{/* state的值和input的value属性绑定
给input绑定onChange事件
*/}
用户名:
描述:
城市:
性别: 男
女
{this.state.sex}
)
}
handleChange = (ev) => {
// 获取当前的表单元素
const target = ev.target
// 获取当前元素的name属性值
const { name, value, type, checked } = target
// 根据表单元素的type类型来判断是否为checkbox,来断定绑定的是checked还是value
const val = type === 'checked' ? checked : value
this.setState({
[name]: val,
})
}
}
export default App
借助于ref属性,通过操作原生DOM来获取表单数据的值,这样的表单元素称为非受控组件
React官方推荐我们使用受控组件来处理表单数据
import { Component, createRef } from 'react'
class App extends Component {
// 1.创建一个ref对象
txtRef = createRef()
render() {
return (
用户名:
{/* 2.将创建好的ref对象添加到输入框上 */}
)
}
handleClick = (ev) => {
// 3.通过ref对象获取到文本框的值
console.log(this.txtRef.current.value)
}
}
export default App
思路:
父组件
import { Component } from 'react'
// 引入子组件
import Child from './Child'
import Son from './Son'
class App extends Component {
state = {
msg: '我是父组件的数据',
}
render() {
return (
父组件
{/* 1.子组件上绑定自定义属性 */}
)
}
}
export default App
子组件–类组件
import { Component } from 'react'
class Child extends Component {
render() {
console.log(this.props)
return (
子组件
{/* 2.在子组件中通过this.props来接收传递过来的数据 */}
{this.props.msg}
)
}
}
export default Child
子组件–函数组件
function Son(props) {
return {props.msg}
}
export default Son
在类组件中,如果使用了constructor构造函数,**应该将props传递给super,**否则无法在构造函数中获取到props
class Child extends Component {
// 如果使用了constructor构造函数,应该将props
// 传递给super,否则无法在构造函数中获取到props
constructor(props) {
// 将props传递给super
super(props)
console.log(props, 'constructor')
}
render() {
console.log(this.props)
return (
子组件
{/* 2.在子组件中通过this.props来接收传递过来的数据 */}
{this.props.msg}
)
}
}
//父组件
import { Component } from 'react'
// 引入子组件
import Child from './Child'
import Son from './Son'
class App extends Component {
state = {
msg: '我是父组件的数据',
}
render() {
return (
父组件
{/* 1.子组件*/}
我是写在子组件标签元素内的元素
)
}
}
export default App
//子组件
import { Component } from 'react'
class Child extends Component {
render() {
console.log(this.props)
return (
子组件
{/* 在子组件中通过this.props.children来接收写在子组件标签内的数据 */}
{this.props.children}
)
}
}
export default Child
//父组件
import { Component } from 'react'
// 引入子组件
import Child from './Child'
import Son from './Son'
class App extends Component {
state = {
msg: '我是父组件的数据',
}
render() {
return (
父组件
{/* 1.在标签上绑定自定义属性,值为html标签*/}
左边}
right={}
>
)
}
}
export default App
//子组件
import { Component } from 'react'
class Child extends Component {
render() {
console.log(this.props)
return (
子组件
{this.props.left}
我是子组件中的内容
{this.props.right}
)
}
}
export default Child
有时候别人在使用我们封装好的组件的时候,传递的值和我们预期的值可能不一样,这样就会导致错误
,为了能够让用户快速的找到报错的原因及时修改,我们要对我们接收的props的值进行验证
步骤:
安装prop-types第三方包
npm i prop-types
or
yarn add prop-types
导入prop-typs包
使用组件名.propTypes={}来给组件的props添加校验规则
校验规则通过PropTypes对象来指定
props属性值的约束规则
常见类型 array,bool,func,number,object,string
React元素的类型: element
必填项:isRequired
特定结构的对象:shape({})
//父组件
import { Component } from 'react'
// 引入子组件
import Child from './Child'
class App extends Component {
state = {
msg: '我是父组件的数据',
arr: [11, 22, 33],
obj: {
name: 'zs',
age: 12,
},
}
render() {
return (
父组件
)
}
}
export default App
//子组件
import { Component } from 'react'
// 1.导入prop-typs第三方包
import propTypes from 'prop-types'
class Child extends Component {
render() {
console.log(this.props)
return (
子组件
{this.props.msg}
)
}
}
// 2.通过组件名.propTypes添加校验规则
Child.propTypes = {
// 约束msg的值为string类型
msg: propTypes.bool,
// 必填项
arr: propTypes.array.isRequired,
// 特定结构的对象
// 要求person必须是一个对象,对象中必须包含name属性和age属性,值分别是string和number类型
person: propTypes.shape({
name: propTypes.string,
age: propTypes.number,
}),
}
/*
props属性值的约束规则
常见类型 array,bool,func,number,object,string
React元素的类型: element
必填项:isRequired
特定结构的对象:shape({})
*/
export default Child
有时候为了让我们组件使用起来更方便,需要给组件设置默认值,在不传递该属性时生效,传递后以传递的值为准
语法 组件名.defaultProps
import { Component } from 'react'
// 1.导入prop-typs第三方包
import propTypes from 'prop-types'
class Child extends Component {
render() {
console.log(this.props)
return (
子组件
{this.props.msg}
)
}
}
// 2.通过组件名.propTypes添加校验规则
Child.propTypes = {
// 约束msg的值为string类型
msg: propTypes.string,
// 必填项
arr: propTypes.array.isRequired,
// 特定结构的对象
// 要求person必须是一个对象,对象中必须包含name属性和age属性,值分别是string和number类型
person: propTypes.shape({
name: propTypes.string,
age: propTypes.number,
}),
}
// 给msg属性设置默认值
Child.defaultProps = {
msg: '我是msg的默认值',
}
export default Child
父组件
import { Component } from 'react'
// 引入子组件
import Child from './Child'
class App extends Component {
state = {
msg: '我是父组件的数据',
n: 0,
}
render() {
return (
父组件
{this.state.n}
{/*
2.在子组件的标签上绑定一个自定义属性,
值为定义的回调函数
*/}
)
}
// 1.在父组件中定义一个回调函数
getChildData = (value) => {
// value就是子组件传递过来的数据
console.log(value)
this.setState({
n: value,
})
}
}
export default App
子组件
import { Component } from 'react'
class Child extends Component {
state = {
num: 10,
}
render() {
return (
子组件
)
}
handleClick = () => {
// 3通过this.props来调用这个回调函数
// 并将传递的数据(子传父的数据)作为参数传递给
// 该函数
this.props.getChildData(this.state.num)
}
}
export default Child
父组件
import { Component } from 'react'
// 引入子组件
import Child from './Child'
import Son from './Son'
class App extends Component {
// 1. 在父组件中定义共享数据
state = {
n: 0,
}
render() {
return (
父组件
{this.state.n}
{/* 3.将回调函数传递给组件Child(组件A) */}
)
}
// 1.在父组件中定义一个回调函数
addN = (value) => {
this.setState({
n: this.state.n + value,
})
}
}
export default App
组件A
import { Component } from 'react'
class Child extends Component {
state = {
num: 10,
}
render() {
return (
组件A
)
}
handleClick = () => {
// 4.在组件A中通过this.props来调用函数
this.props.add(this.state.num)
}
}
export default Child
组件B
function Son(props) {
return (
{/* 在组件B中通过props接收共享数据 */}
{props.num}
)
}
export default Son
bus.js
// 1.导入createContext方法
import { createContext } from 'react'
// 2 创建Mcontext对象并设置默认值,也可以不设置,默认值在不提供provider组件时生效
const MyContext = createContext('我是默认值')
// 3.创建Provider组件 (提供数据) Consumer(消费数据)组件
const { Provider, Consumer } = MyContext
export default MyContext
export { Provider, Consumer }
根组件 App.js
import { Component } from 'react'
// 引入子组件
import Child from './Child'
// 1.引入Provider组件来提供数据
import { Provider } from './bus'
class App extends Component {
render() {
return (
// 2.Provider组件作为根组件,并使用value属性提供数据
父组件
)
}
}
export default App
Son.js
import { Component } from 'react'
// 引入 MyContext对象
import MyContext from './bus'
class Son extends Component {
// 3.指定contextType读取当前创建的context对象
// static 定义静态属性
static contextType = MyContext
render() {
return (
Son组件
{/* 通过this.context渲染 */}
{this.context}
)
}
}
export default Son
// 函数组件通过Consumer来渲染数据
import { Consumer } from './bus'
function Son(props) {
return (
Son组件
{/* Consumer组件的子节点是一个函数,返回html,
函数的参数就是Provider组件提供的数据
*/}
{(data) => }
)
}
export default Son
创建(挂载)阶段(组件创建时)
执行顺序:constructor----->render------->componentDidMount
constructor
时机:创建组件时最先执行;
作用:1:初始化state2:为事件处理程序绑定this
render
时机:每次组件渲染都会触发
作用:渲染视图**(注意:不能调用setState)**
componentDidMount
时机:组件挂载(完成DOM渲染后)
作用:1:发送网络请求2:DOM操作
代码
import { Component } from 'react'
class App extends Component {
constructor() {
super() //this
console.log('constructor')
}
render() {
console.log('render')
return (
AP片组件
)
}
componentDidMount() {
console.log('componentDidMount')
}
}
export default App
更新阶段
使用this.setState更新状态
组件的props属性发生变化
强制更新,调用forceupdate()
执行顺序:render-------->componentDidUpdate
render
时机:每次组件渲染都会触发
作用:渲染视图**(注意:不能调用setState)**
componentDidUpdate
时机:组件更新(完成DOM渲染后)
作用:1:发送网络请求2:DOM操作
注意:如果要setState()必须放在一个if操作中
属性: componentDidUpdate(prveProps,prevState)
prevProps 更新之前的props值 prevState更新之前的state值 我们可以对比更新之前的props和state和更新之后的props和state是否一致,来决定是否调用 setState
- 1
- 2
- 3
代码
import { Component } from 'react'
class Child extends Component {
state = {
count: 0,
}
render() {
console.log('render', 'Child')
return (
Child组件
{this.props.num}---{this.state.count}
)
}
componentDidUpdate(prevProps, prevState) {
// prevProps 更新之前的props值
// prevState更新之前的state值
// 我们可以对比更新之前的props和state和更新之后的props和state是否一致,
// 来决定是否调用setState
console.log('componentDidUpdate')
console.log(prevProps, this.props)
console.log(prevState, this.state)
}
plus = () => {
this.setState({
count: this.state.count + 5,
})
}
}
export default Child
卸载阶段
componentWillUnMount
时机:组件卸载(从页面中消失)
作用:执行清理工作(清理定时器,事件)
父组件
import { Component } from 'react'
import Child from './Child'
class App extends Component {
constructor() {
super() //this
this.state = {
num: 10,
isshow: true,
}
}
render() {
return (
AP片组件
{this.state.isshow ? : null}
)
}
toggle = () => {
this.setState({
isshow: !this.state.isshow,
})
}
add = () => {
this.setState({
num: this.state.num + 1,
})
}
}
export default App
子组件
import { Component } from 'react'
class Child extends Component {
state = {
count: 0,
}
render() {
console.log('render', 'Child')
return (
Child组件
{this.props.num}---{this.state.count}
)
}
componentDidUpdate(prevProps, prevState) {
// prevProps 更新之前的props值
// prevState更新之前的state值
// 我们可以对比更新之前的props和state和更新之后的props和state是否一致,
// 来决定是否调用setState
console.log('componentDidUpdate')
console.log(prevProps, this.props)
console.log(prevState, this.state)
}
componentWillUnmount() {
console.log('componentWillUnmount')
}
plus = () => {
this.setState({
count: this.state.count + 5,
})
}
}
export default Child
完整的生命周期钩子函数
static getDerivedStateFromProps
getDerivedStateFromProps属于静态方法,所以不能在该方法中访问组件实例
执行时机:组件创建时,组件更新时
执行顺序:该方法在render方法之前执行
该方法必须返回一个对象来更新state,如果返回null则不更新任何内容
static getDerivedStateFromProps(props,state)
App.js
import { Component } from 'react'
import List from './List'
import FooterCmp from './FooterCmp'
class App extends Component {
state = {
arrList: [
{ id: 1, checked: false, name: 'aaaa' },
{ id: 2, checked: false, name: 'bbbb' },
{ id: 3, checked: false, name: 'cccc' },
{ id: 4, checked: false, name: 'dddd' },
{ id: 5, checked: false, name: 'eeee' },
],
}
render() {
return (
)
}
handleChange = (id) => {
// 单选
// console.log(id)
const copyArrList = [...this.state.arrList]
const idx = copyArrList.findIndex((item) => item.id === id)
copyArrList[idx].checked = !copyArrList[idx].checked
this.setState({
arrList: copyArrList,
})
}
// 全选
chooseAll = (checked) => {
const copyArrList = [...this.state.arrList]
copyArrList.forEach((item) => {
item.checked = checked
})
this.setState({
arrList: copyArrList,
})
}
}
export default App
List.js
import { Component } from 'react'
class List extends Component {
render() {
return (
{this.props.arrList.map((item) => (
this.handleChange(item.id)}
/>
{item.name}
))}
)
}
handleChange = (id) => {
this.props.handleList(id)
}
}
export default List
FooterCmp.js
import { Component } from 'react'
class FooterCmp extends Component {
state = {
checked: false,
}
static getDerivedStateFromProps(nextProps, nextState) {
// nextProps 下一次的props属性值
// nextState下一次的state属性值
// 当父组件传递过来的arrList数组发生变化时返回最新的state
return {
checked: nextProps.arrList.every((item) => item.checked),
}
}
render() {
return (
全选:
)
}
changeChecked = (ev) => {
this.props.chooseAll(ev.target.checked)
// 更改state中checked的值
}
}
export default FooterCmp
shoundComponentUpdate
组件重新渲染前执行
根据shoundComponentUpdate的返回值来决定是否更新自身组件以及子组件,返回true更新返回false不更新
此方法作为性能优化的一种方案,不能企图依赖该方法来阻止渲染
最好使用React提供的内置组件PureComponent来自动判断是否调用render方法,而不是使用shoundComponentUpdate方法来进行手动判断
不建议在shoundComponentUpdate中进行深层比较或者使用JSON.stringify
shoundComponentUpdate(nextProps,nextState)
nextProps更新完成后的props值
nextState更新完成后的state值
代码
import { Component } from 'react'
class App extends Component {
state = {
num: 0,
}
shouldComponentUpdate(nextProps, nextState) {
// 判断上一次的state和更新后的state是否相等,来决定是否调用render来更新组件
return nextState !== this.state.num
}
render() {
console.log('render')
return (
父组件
{this.state.num}
)
}
handleClick = () => {
this.setState({
num: parseInt(Math.random() * 3),
})
}
}
export default App
import { Component } from 'react'
class App extends Component {
componentDidMount() {
//dom渲染完成
// 开启定时器
// timerid 存储到this中,而不是state中
this.timerId = setInterval(() => {}, 1000)
}
componentWillUnmount() {
// 卸载
clearInterval(this.timerId)
}
render() {
return
}
}
export default App
避免不必要的重新渲染
使用shoundComponentUpdate钩子函数手动更新组件
使用纯组件 PureComponent自动更新组件,使用方式和componet一致
PureComponent会分别对前后两次props和state进行浅层对比
浅层比较:
对于基本数据类型直接比较两个值是否相同
引用数据类型比较的是地址,所以不要直接修改state中的引用数据类型,应该创建一个新的实例
import { PureComponent } from 'react'
class App extends PureComponent {
state = {
num: 0,
}
render() {
console.log('render')
return (
{ padding: '0 10px' }}>{this.state.num}
)
}
handleClick = () => {
this.setState({
num: parseInt(Math.random() * 3),
})
}
}
export default App
我们访问的路径和React组件之间的对应关系
npm i react-router-dom@5
BrowserRouter使用历史模式history来管理路由
HashRouter使用哈希模式hash来管理路由
// 将引入的BrowserRouter模块取一个别名 叫做Router
import { BrowserRouter as Router } from 'react-router-dom'
React-router-dom路由系统要求一个路由系统只能有一个Router
root.render(
{/* 将App组件用Router包裹起来 */}
)
import { Component } from 'react'
export default class Course extends Component {
render() {
return Home
}
}
import { Component } from 'react'
export default class Home extends Component {
render() {
return Home
}
}
// 引入Route模块
import { Route } from 'react-router-dom'
import Course from './Course'
import Home from './Home'
class App extends PureComponent {
render() {
return (
)
}
}
// 引入Route模块
import { Link, Route } from 'react-router-dom'
import Course from './Course'
import Home from './Home'
class App extends PureComponent {
render() {
return (
{/* 路由跳转
Link模块的to属性可以是一个字符串,也可以是一个对象
*/}
{/* 首页 */}
{ pathname: '/home' }}>首页
课程
)
}
}
当我们希望给当前的路由添加一个样式的时候,我们可以使用NavLink模块来代替Link模块
给NavLink添加activeClassName属性,属性名为class名,在样式中定义active样式
App.js
import { PureComponent } from 'react'
import './App.css'
// 引入Route模块
import { Link, NavLink, Route } from 'react-router-dom'
import Course from './Course'
import Home from './Home'
class App extends PureComponent {
render() {
return (
{/* 路由跳转*/}
首页
课程
{/* 路由出口 */}
)
}
}
export default App
App.css
a {
text-decoration: none;
margin: 0 10px;
}
.active {
color: #c00;
}
我们希望一进入页面就可以访问到/home页面,使用Redirect模块
// 引入Route模块
import { Link, NavLink, Route, Redirect } from 'react-router-dom'
import Course from './Course'
import Home from './Home'
export default class App extends PureComponent {
render() {
return (
{/* 路由跳转*/}
首页
课程
{/* 路由出口 */}
{/* 访问根路径的时候,重定向到/home */}
{/* from 从那个路径来 to 要跳转到那个路径 */}
)
}
}
目前路由存在问题,切换到Course页面,当我们刷新页面的时候就回到了Home页面
解决上面问题使用Switch模块
import { Link, NavLink, Route, Redirect, Switch } from 'react-router-dom'
import Course from './Course'
import Home from './Home'
export default class App extends PureComponent {
render() {
return (
{/* 路由跳转*/}
首页
课程
{/* 将路由表用Switch组件包裹起来 */}
{/* 路由出口 */}
{/* 访问根路径的时候,重定向到/home */}
{/* from 从那个路径来 to 要跳转到那个路径 */}
)
}
}
解释:
我们的路由表会从头到尾执行一遍,即使他已经匹配到当前路径,但是它也会把下面的路由执行一遍,
那么久导致了我们最后无论如何都会执行到Redirect,就被重新定向到Home页面了
Switch作用就是让当前路由如果匹配到,就不再向下继续匹配
我们访问一个不存在的路径,我们希望把这个不存在的路径重定向到404页面
定义一个NotFound页面
import { Component } from 'react'
export default class NotFound extends Component {
render() {
return NotFound
}
}
通过Route定义访问任何路径,都去访问NotFound页面
{/* 将路由表用Switch组件包裹起来 */}
{/* 路由出口 */}
{/* 访问根路径的时候,重定向到/home */}
{/* from 从那个路径来 to 要跳转到那个路径 */}
问题:我们并没有机会访问到NotFound页面,每次访问不存在的路径都会重定向到Home页面
因为我们的根路径/会匹配到任何路径,任何路径都是以 / 开头的,我们把 /这样的路径叫做父路径
/home /course叫做 / 的子路径。父路径会匹配它下面所有的子路径,是模糊匹配
比如 /home会匹配到 /home/aaa ,/home/bbb;因为 /home/aaa ,/home/bbb是/home的子路径
所以在Redirect中需要
精确匹配
到/ ,而 / 的子路径不能被匹配到,我们需要使用exact属性
{/* 将路由表用Switch组件包裹起来 */}
{/* 路由出口 */}
{/* 访问根路径的时候,重定向到/home */}
{/* from 从那个路径来 to 要跳转到那个路径 */}
声明式导航
通过NavLink来实现路由跳转的导航
编程式导航
通过js来实现路由的跳转
export default class NotFound extends Component {
render() {
return (
)
}
handleClick = () => {
console.log(this.props)
this.props.history.push('/course')
}
}
history对象的常用方法
- push路由跳转,push里面传递的参数和NavLink中的to属性的参数是一样的
- go(num) 前进或后退num级
- goBack() go(-1) 后退一级
- goForward() go(1) 前进一级
//App.js
export default class App extends PureComponent {
render() {
return (
{/* 路由跳转*/}
首页
新闻
课程
)
}
}
课程下配置二级路由
import { Component } from 'react'
import { NavLink, Redirect, Route, Switch } from 'react-router-dom'
import JsCourse from './course/JsCourse'
import VueCourse from './course/VueCourse'
import ReactCourse from './course/ReactCourse'
export default class Course extends Component {
render() {
return (
Course
js课程
vue课程
react课程
)
}
}
动态路由传参有三种方式:
注册路由的时候通过:params的形式来声明
{/* 通过 :id来声明我们要传递的参数是id */}
路由匹配
{this.state.arr.map((item) => (
-
{item.tit}
))}
{this.state.arr.map((item) => (
- {
this.props.history.push('/detail/' + item.id)
}}
key={item.id}
>
{item.tit}
))}
在组件中获取params方式传递参数
const { id } = this.props.match.params
query方式传递的参数不需要在注册路由的时候声明
路由匹配
{this.state.arr.map((item) => (
-
{item.tit}
))}
在组件中获取query方式传递的参数
this.props.location.search.split('=')[1]
state方式传递的参数也不需要在注册路由的时候声明
路由匹配
{this.state.arr.map((item) => (
-
{ pathname: '/dstate', state: { id: 5 } }}>
{item.tit}
))}
在组件中获取传递的参数
this.props.location.state
只有被路由管理的组件的props属性上才具有路由的三大对象:history,location,match
HomeChild.js
import { Component } from 'react'
export default class HomeChild extends Component {
componentDidMount() {
// 因为HomeChild这个组件是直接渲染在Home组件中,并不是通过Route组件渲染处理
// 所以HomeChild这个组件并没有被路由所管理,该组件的props不具有路由的三大属性
console.log(this.props, 'homechild') // {}
}
render() {
return (
我是home组件的子组件
)
}
}
Home.js
import { Component } from 'react'
import HomeChild from './HomeChild'
export default class Home extends Component {
render() {
return (
home
)
}
}
import { Component } from 'react'
import HomeChild from './HomeChild'
export default class Home extends Component {
render() {
return (
home
{/* 将父组件的props传递给子组件 */}
)
}
}
withRouter模块可以让没有被路由管理的组件也可以获取到路由的三大对象
//HomeChild.js
import { Component } from 'react'
import { withRouter } from 'react-router-dom'
class HomeChild extends Component {
componentDidMount() {
// 因为HomeChild这个组件是直接渲染在Home组件中,并不是通过Route组件渲染处理
// 所以HomeChild这个组件并没有被路由所管理,该组件的props不具有路由的三大属性
console.log(this.props, 'homechild') // {}
}
render() {
return (
我是home组件的子组件
)
}
}
// withRouter方法接收一个组件作为参数,返回一个具有路由三大对象的组件
export default withRouter(HomeChild)
React中的路由懒加载是基于React.lazy方法和Suspense组件来实现的
import React, { PureComponent, Suspense } from 'react'
import './App.css'
// 引入Route模块
import { Link, NavLink, Route, Redirect, Switch } from 'react-router-dom'
const Course = React.lazy(() => import('./Course'))
const Home = React.lazy(() => import('./Home'))
const News = React.lazy(() => import('./News'))
const Detail = React.lazy(() => import('./Detail'))
const Details = React.lazy(() => import('./Details'))
const Dstate = React.lazy(() => import('./Dstate'))
const NotFound = React.lazy(() => import('./NotFound'))
export default class App extends PureComponent {
render() {
return (
{/* 路由跳转*/}
首页
新闻
课程
加载中... }>
{/* 将路由表用Switch组件包裹起来 */}
{/* 路由出口 */}
{/* 通过 :id来声明我们要传递的参数是id */}
{/* 访问根路径的时候,重定向到/home */}
{/* from 从那个路径来 to 要跳转到那个路径 */}
登录拦截案例
Login.js
import React, { Component } from 'react'
import axios from 'axios'
export default class Login extends Component {
state = {
username: '',
password: '',
}
render() {
return (
)
}
// 受控组件
handleChange = (ev) => {
const el = ev.target
this.setState({
[el.name]: el.value,
})
}
login = async () => {
// 发送登录请求
const {
data: { data: res, meta },
} = await axios.post(
'http://shiyansong.cn:8888/api/private/v1/login',
this.state
)
// console.log(res)
// 根据返回的状态码判断是否登录成功
if (meta.status === 200) {
// 登录成功将token保存到本地
localStorage.setItem('token', res.token)
// 跳转到首页
this.props.history.push('/home')
}
}
}
Home.js
import React, { Component } from 'react'
export default class Home extends Component {
render() {
return (
首页面
)
}
logout = () => {
localStorage.removeItem('token')
this.props.history.push('/login')
}
}
App.js
import React, { PureComponent, Suspense } from 'react'
import './App.css'
// 引入Route模块
import { Route, Redirect, Switch } from 'react-router-dom'
const Login = React.lazy(() => import('./Login'))
const Home = React.lazy(() => import('./Home'))
const NotFound = React.lazy(() => import('./NotFound'))
export default class App extends PureComponent {
// 验证是否登录成功的函数
loginVerfiy = () => {
if (localStorage.getItem('token')) {
return true
} else {
return false
}
}
render() {
return (
加载中... }>
{/* render属性来渲染组件 */}
this.loginVerfiy() ? :
}
>