初识JSX
React
主要使用的就是jsx
语法来编写dom
,所以我们要先认识jsx
,然后再开始学习两者相结合jsx
其实就是在JS中编写HTML的一种写法- 编写
jsx
要注意以下几个规则:
- 定义虚拟DOM时,不要写引号
- 标签中混入JS表达式时要用=={}==
- 样式的类名指定不要用class,要用className
- 内联样式,要用==style={{key:value}}==的形式去写
- 只有一个根标签
- 标签必须闭合
- 标签首字母
- 若小写字母开头,则将该标签转为
html
中同名元素,若html中无该标签对应的同名元素,则报错- 若大写字母开头,
react
就去渲染对应的组件,若组件没有定义,则报错。
初学React
1、小案例
我们使用
React
编写一个Hello World
的小案例,这里演示使用的是React16.8.1
版本注意:react和react-dom的版本一定要对应!
案例中使用
js
文件引入依赖,也可以使用CDN
引入:BootCDN - Bootstrap 中文网开源项目免费 CDN 加速服务CDN中,要选
umd
下的,不要选cjs
下的
babel.min.js
文件地址(可使用script
标签,或下载到本地):https://unpkg.com/@babel/standalone/babel.min.js
1.1、文件结构
1.2、代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
<div id="test"></div>
<!-- 注意:这里类型一定要写 text/babel,这样才会被babel编译 -->
<script type="text/babel">
// 这里一定不要带引号,不是js里面的字符串
const VNODE = <h1>Hello World</h1>
// 使用ReactDOM.render渲染
// 第一个参数:要渲染的虚拟dom
// 第二个参数:要渲染的dom节点
// 注意:react没有提供选择器参数,只能自己获取节点,然后才能挂载上去
ReactDOM.render(VNODE, document.getElementById('test'))
</script>
</body>
</html>
1.3、效果
2、渲染DOM的两种写法
2.1、JSX(常用)
- 第一种就是上面写的
JSX
方式,写法简单,易懂- 其实他实际会被转换为下面的写法,不过不需要咱们写,引用的
babel
会自动转换
const VNODE = <h1>Hello World</h1>
2.2、h函数
- 第二种就是
react
提供的另一个渲染方式React.createElement
- 这就是
vue
中也提供的h
函数- 一个节点的话看起来还好,如果有多个元素,并且嵌套子元素,写起来非常麻烦,读起来也难懂,所以基本不用!
const VNODE = React.createElement('h1', {id: 'title'}, 'Hello World')
3、虚拟DOM与真实DOM
虚拟DOM
本质是Object
类型的对象虚拟DOM
比较**“轻”,真实DOM
比较"重"**,因为虚拟DOM
是React
内部在用,无需真实DOM
上那么多的属性虚拟DOM
最终会被React
转化为真实DOM
,呈现在页面上
4、JSX小练习
- 模拟一些集合数据,然后使用
JSX
渲染ul、li
标签到页面上- 注意:[]
jSX
的{}
中只能写js表达式,- 被遍历的
jsx标签
最外层元素必须要有一个唯一值key
js语句(代码)
与js表达式
- 表达式:
- 一个表达式会产生一个值,可以放在任何一个需要值的地方,下面这些都是表达式
- a
- a+b
- demo(1)
- arr.map()
- function test() {}
- 语句(代码):
- if(){}
- for(){}
- switch(){}
- 如果代码要换行的话,最好把这一整段代码,用
()
包起来(<div>xxxxx</div>)
- 另外,如果要在jsx中写注释,要这么写才会被正确解析为注释
{ /* SDFSDF */ }
,外层用{}
,里面写js的注释
4.1、写死
- 写死肯定是不行的,因为这些遍历的数据都是通过查询获得的,你并不知道数据是什么,下面只进行演示
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
<div id="test"></div>
<!-- 注意:这里类型一定要写 text/babel,这样才会被babel编译 -->
<script type="text/babel">
// 模拟假数据
let dataArr = ['Vue', 'React', 'Angular', 'Java', 'Python']
const VNODE = (
<ul>
<li>Vue</li>
<li>React</li>
<li>Angular</li>
<li>Java</li>
<li>Python</li>
</ul>
)
ReactDOM.render(VNODE, document.getElementById('test'))
</script>
</body>
</html>
效果
4.2、中间穿插JS
- 首先在
JSX
中使用JS
,外面要包一层{}
- 其次数据要遍历,肯定要使用循环,但是上面说了==
for
循环是语句,不是表达式==,所以我们可以使用语句里的arr.map()
方法- 然后使用
map
方法遍历数据,封装标签。标签里面依旧使用{}
来使用item(遍历的每一项)- 最后要给遍历封装的标签每个都添加
唯一值的key
属性(原理和Vue一样,后面会讲),不加的话虽然不影响渲染,但是控制台会报错
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
<div id="test"></div>
<!-- 注意:这里类型一定要写 text/babel,这样才会被babel编译 -->
<script type="text/babel">
// 模拟假数据
let dataArr = ['Vue', 'React', 'Angular', 'Java', 'Python']
const VNODE = (
<ul>
{
dataArr.map((item, index) => <li key={index}>{item}</li>)
}
</ul>
)
ReactDOM.render(VNODE, document.getElementById('test'))
</script>
</body>
</html>
效果
5、模块与组件、模块化与组件化
- 模块就是把公共的或者复杂的==
JS
代码==提取出来单独的文件- 组件就是把公共的或者复杂的==
HTML
代码(包含html、css、js)==提取出来单独的文件
5.1、模块
- 理解:向外提供特定功能的
js
程序,一般就是一个js文件- 随着业务逻辑增加,代码越来越多并且复杂,如果都集中在一个文件,那么后期必定很难维护、写起来也费劲
- 作用:复用
js
,简化js
的编写,提高js
运行效率- 典型的引入方式有三种
- 使用原生
<script type="text/javascript" src="xxx/xxx.js"></script>
- 使用
ES6
提供的import xxx from 'xxx/xxx.js'
(常用)- 使用
CommonJS
提供的const a = require(xxx/xxx.js)
5.2、组件
- 理解:用来实现局部功能效果的代码和资源的集合(
html/css/js/image
)等这个模块需要的资源- 随着页面的增加以及各种各样的样式呈现,一个
html
肯定有无数个dom
,以及对应的功能,如果都集中在一个文件,那么后期必定很难维护、写起来也费劲,并且其他页面也不能复用- 作用:实现代码复用,精简页面代码复杂度
React
中编写组件可以使用两种方式,后面会逐一讲解演示
- 类式定义组件(功能多,写法稍微复杂)
- 函数式定义组件(功能少,写法简单)
5.3、模块化
当一个项目都是使用
JS模块
来编写的,那么这个应用就是模块化的一个应用
5.4、组件化
当一个项目都是使用
组件
进行编写的,那么这个应用就是一个组件化的应用
6、开发者工具
Vue
有自己的开发者工具([Vue.js devtools](Vue.js devtools - Microsoft Edge Addons)),帮助开发者观察响应式数据变化,以及组件代码树的可视化React
也有自己的开发者工具([React Developer Tools](React Developer Tools - Microsoft Edge Addons))- 以上地址皆是
Edge
插件下载地址,需要Chrome
插件地址的话,自行百度- 下载好后,在咱们页面右上角查看插件可以看到这样一个红色的图标,这不是一个正常的图标因为咱们现在还在开发者模式,没有打包,没有进行工程化处理,所以它是这么显示的
- 后面我们会用到工程化搭建
React
项目,到时候图标就正常了
7、函数式组件(功能少,简单)
- 使用函数定义组件,组件名称(函数名)首字母必须为大写!!,如果首字母为小写的化,那么会将其看作普通标签对待
- 放到
render
函数中的时候,要以标签的形式(单/双)都可以- 函数必须返回
JSX
代码作为渲染内容!React
解析组件标签,找到了Demo
组件,但是发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中- 其他功能后面会讲到,这里只演示渲染
7.1、代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
<div id="test"></div>
<!-- 注意:这里类型一定要写 text/babel,这样才会被babel编译 -->
<script type="text/babel">
// 首字母必须为大写
function Demo() {
return <h2>函数定义的组件,适用于简单组件的定义!</h2>
}
// 要用标签的形式
ReactDOM.render(<Demo></Demo>, document.getElementById('test'))
</script>
</body>
</html>
7.2、效果
8、类式组件(功能多,常用)
- 类的相关知识这里就不讲解了,可以参考网上视频多研究下
JS
中的类
概念- 上面讲解了简单功能少的
函数式组件
,这里我们讲解常用的类式组件
,就是相当于,一个类就是一个组件!- 类写法有以下必须条件:
- 类首字母也必须大写
- 必须要继承
React.Component
类- 类中必须要有
render
函数,并且必须返回JSX
代码作为渲染内容- 放到
render
函数中的时候,要以标签的形式(单/双)都可以React
解析组件标签,找到了Demo
组件,然后发现组件是使用类定义的,随后new
出来该类的实例,并通过该实例调用到原型上的render
方法,最后将render
返回的虚拟DOM
转为真实DOM
,随后成呈现在页面中。render
方法中的this
,其实是类Demo
的的实例对象
8.1、代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
<div id="test"></div>
<!-- 注意:这里类型一定要写 text/babel,这样才会被babel编译 -->
<script type="text/babel">
// 首字母必须为大写,必须继承 React.Component
class Demo extends React.Component {
// 放在原型对象上,供实例使用 new Demo().render()。虽然你没有创建实例,但是React会帮你创建并调用render方法
render() {
// render中的this,就是react帮你创造出来的实例对象
console.log("render中的this", this);
return (
<div>
<h1>类定义的组件,适用于 复杂组件 的定义</h1>
</div>
)
}
}
// 要用标签的形式
ReactDOM.render(<Demo/>, document.getElementById('test'))
</script>
</body>
</html>
8.2、效果
9、组件三大核心之一:state
- 理解:
state
是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)- 组件被称为"状态机",通过更新组件的
state
来更新对应的页面提示(重新渲染组件)- 注意:
- 组件中
render
方法中的this
为组件实例对象- 组件自定义的方法中
this
为undefined
,如何解决?
- 强制绑定this:通过函数对象的
bind()
- 箭头函数
- 状态数据,不能直接修改或更新
- 下面使用一个案例来给大家演示下
- 页面展示
今天很幸运
,点击文字后,切换为今天很倒霉
9.1、初始化state
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
<div id="test"></div>
<!-- 注意:这里类型一定要写 text/babel,这样才会被babel编译 -->
<script type="text/babel">
// 首字母必须为大写,必须继承 React.Component
class Demo extends React.Component {
constructor(props) {
// 在使用this时,要先调用super()方法
super(props);
// 建议最好定义为对象,因为一般不会只有一个动态值
this.state = {isLucky: true};
// 解决change方法中的this,方法一:使用bind()
this.change = this.change.bind(this);
}
// 这里面的this 指向的是当前组件实例,里面有咱们定义好的state对象
render() {
// 下面使用state的时候可以优化为如下解构取值
const {isLucky } = this.state;
return (
// 这里不要使用this.change(),否则会直接调用该方法,拿该方法的返回值`undefined`去当作onClick执行的方法
// 注意,这里使用的不是原生的onclick,而是React封装的onClick方法!,其他原生方法也一致,比如:onChange,onSubmit,onBlur等等
<div onClick={this.change}>
<h1>今天很 { this.state.isLucky ? '幸运' : '不幸运'}</h1>
</div>
)
}
// 点击修改值的函数,注意:这里访问不到this,因为不是由实例对象调用的!!!
change() {
// 这是继承的类 React.Component 中提供的方法,用来修改state的值,并且会自动重新渲染,其余修改值的方法不会造成页面重新渲染
// 方法里面传入对象,对象里面可以写多个值,会自动合并!!!,并不会覆盖整个对象
this.setState({
isLucky: !this.state.isLucky
})
}
}
// 要用标签的形式
ReactDOM.render(<Demo/>, document.getElementById('test'))
</script>
</body>
</html>
效果
9.2、优化代码
- 上面的
state
定义可以优化- 在
change
方法中使用this
,有两种方法,上面只写了一种- 优化到最后可以发现,基本情况下
constructor
构造器可以省略掉
9.2.1、优化state
- 熟悉
JS
中的Class
的会知道,类里面可以直接定义属性以及初始值,无需在构造器中定义赋值- 那么我们就可以将自己定义的
state
对象直接当作类属性编写,即可在构造器中移除这段代码,代码如下,注意state
定义的位置
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
<div id="test"></div>
<!-- 注意:这里类型一定要写 text/babel,这样才会被babel编译 -->
<script type="text/babel">
// 首字母必须为大写,必须继承 React.Component
class Demo extends React.Component {
constructor(props) {
// 在使用this时,要先调用super()方法
super(props);
// 解决change方法中的this,方法一:使用bind()
this.change = this.change.bind(this);
}
// 简化state,将state直接写入属性中,并赋初始值
state = {
isLucky: true
}
// 这里面的this 指向的是当前组件实例,里面有咱们定义好的state对象
render() {
// 下面使用state的时候可以优化为如下解构取值
const {isLucky } = this.state;
return (
// 这里不要使用this.change(),否则会直接调用该方法,拿该方法的返回值`undefined`去当作onClick执行的方法
// 注意,这里使用的不是原生的onclick,而是React封装的onClick方法!,其他原生方法也一致,比如:onChange,onSubmit,onBlur等等
<div onClick={this.change}>
<h1>今天很 { this.state.isLucky ? '幸运' : '不幸运'}</h1>
</div>
)
}
// 点击修改值的函数,注意:这里访问不到this,因为不是由实例对象调用的!!!
change() {
// 这是继承的类 React.Component 中提供的方法,用来修改state的值,并且会自动重新渲染
// 方法里面传入对象,对象里面可以写多个值,会自动合并!!!,并不会覆盖整个对象
this.setState({
isLucky: !this.state.isLucky
})
}
}
// 要用标签的形式
ReactDOM.render(<Demo/>, document.getElementById('test'))
</script>
</body>
</html>
9.2.2、优化change中的this
- 当使用普通函数
change(){}
的时候,这时候change
方法是在对象的原型身上Demo.prototype.change()
- 当使用赋值函数
change = function(){}
的时候,这时候change
方法就是在自身上面,不在原型上了,但是函数依旧是由window
调用的,由于使用了babel
,开启了严格模式,所以里面的this,依旧指向(undefined
)
- 方法使用箭头函数
change = () => {}
的时候,这时候的this
就会找函数外层的this
了,也就是当前类了,也就可以正常获取state
属性了
- 箭头函数的
this
和普通函数的this
指向可以自学下ES6
语法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
<div id="test"></div>
<!-- 注意:这里类型一定要写 text/babel,这样才会被babel编译 -->
<script type="text/babel">
// 首字母必须为大写,必须继承 React.Component
class Demo extends React.Component {
constructor(props) {
// 在使用this时,要先调用super()方法
super(props);
}
// 简化state,将state直接写入属性中,并赋初始值
state = {
isLucky: true
}
// 这里面的this 指向的是当前组件实例,里面有咱们定义好的state对象
render() {
// 下面使用state的时候可以优化为如下解构取值
const {isLucky } = this.state;
return (
// 这里不要使用this.change(),否则会直接调用该方法,拿该方法的返回值`undefined`去当作onClick执行的方法
// 注意,这里使用的不是原生的onclick,而是React封装的onClick方法!,其他原生方法也一致,比如:onChange,onSubmit,onBlur等等
<div onClick={this.change}>
<h1>今天很 { this.state.isLucky ? '幸运' : '不幸运'}</h1>
</div>
)
}
// 当使用箭头函数时,this指向的就不再是调用者(window)了,就是当前函数外层的this了,即指向当前组件实例
change = () => {
this.setState({
isLucky: !this.state.isLucky
})
}
}
// 要用标签的形式
ReactDOM.render(<Demo/>, document.getElementById('test'))
const demo = new Demo()
console.log("tcc", demo);
</script>
</body>
</html>
9.2.3、优化constructor
现在回头看下
constructor
里面就只剩下super
函数的调用了,咱们直接删除构造器即可
10、组件三大核心之二:props
- 使用过
vue
的话,我们都知道,props
是用来给组件传递参数的,React
支持组件化,那么也是由如此配置的,下面咱们学习如何使用
10.1、基础使用
- 上面我们在打印
this
的时候,可以看到里面有一个props
的空对象,这个对象里面用来存放我们给组件传递的值- 给组件传值,我们只需要在组件标签上,通过标签属性的形式传值即可,
React
会自动将上面绑定的值,给放入props
对象中,供组件内部使用- 下面我们封装一个展示用户信息的一个组件,姓名、性别、年龄均由组件传递参数读取显示
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
<div id="test1"></div>
<br>
<div id="test2"></div>
<br>
<div id="test3"></div>
<script type="text/babel">
class Demo extends React.Component {
render() {
// 这里我们之前打印过,有一个属性教props的对象
console.log("tcc", this.props);
const {name, age, sex} = this.props;
return (
<div>
<h2>姓名:{ name }</h2>
<h2>年龄:{ age }</h2>
<h2>性别:{ sex }</h2>
</div>
)
}
}
// 在标签上写的所有属性,都会放到props对象中
ReactDOM.render(<Demo name="张三" age="18" sex="男"/>, document.getElementById('test1'))
ReactDOM.render(<Demo name="李四" age="19" sex="女"/>, document.getElementById('test2'))
ReactDOM.render(<Demo name="王五" age="21" sex="男"/>, document.getElementById('test3'))
</script>
</body>
</html>
效果
10.2、传参类型
- 上面通过打印
props
可以发现,里面属性都是字符串类型- 如果我们想传递其他类型,可以使用
{}
,里面就可以写js表达式了,同理,函数也一样,不过不要在函数后面添加()
即可- 以下案例我们要给所有年龄+1显示,如果像原来一样直接在
age+1
就是"18"+1="181"
了
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
<div id="test1"></div>
<br>
<div id="test2"></div>
<br>
<div id="test3"></div>
<script type="text/babel">
class Demo extends React.Component {
render() {
// 这里我们之前打印过,有一个属性教props的对象
console.log("tcc", this.props);
const {name, age, sex} = this.props;
return (
<div>
<h2>姓名:{ name }</h2>
{ /* 展示的时候,性别+1 */ }
<h2>年龄:{ age+1 }</h2>
<h2>性别:{ sex }</h2>
</div>
)
}
}
// 在标签上写的所有属性,都会放到props对象中,传入除字符串以外其他类型的参数的话,外层需要使用{}包裹起来
ReactDOM.render(<Demo name="张三" age={18} sex="男"/>, document.getElementById('test1'))
ReactDOM.render(<Demo name="李四" age={19} sex="女"/>, document.getElementById('test2'))
ReactDOM.render(<Demo name="王五" age={21} sex="男"/>, document.getElementById('test3'))
</script>
</body>
</html>
效果
10.3、注意事项
10.3.1、props中的属性不能在组件内部修改值
- 在
vue、React
中,都是不可以通过子组件修改父组件的值的!!!
代码
render() {
// 这里我们之前打印过,有一个属性教props的对象
console.log("tcc", this.props);
const {name, age, sex} = this.props;
// 修改组件传过来的值,会报错!!!!
this.props.age++;
return (
<div>
<h2>姓名:{ name }</h2>
{ /* 展示的时候,性别+1 */ }
<h2>年龄:{ age+1 }</h2>
<h2>性别:{ sex }</h2>
</div>
)
}
效果
10.3、配置
- 这里我们自己写的组件,知道里面的
age
会进行+1
显示,其他人用的话,如果不进入组件内部看,一般不会知道的。- 那么每个用的人都进组件内部看,肯定不现实,那么我们就要对传入的数据进行一些限制
- 如果你的数据没有通过校验,那么就报错到控制台,你一看便知
- 假设,我们要给姓名设置字符串、必填,年龄设置数字类型,性别设置默认值 男,那么请看如下代码
10.3.1、配置数据类型校验
- 我们通过给组件身上添加
propTypes
属性,配置对props
的限制15.5
版本我们直接使用React
自带的React.PropTypes
即可,但是这个版本以后,React
把他单独拆分了出来了一个prop-types
的js依赖- 注意:如果限制类型为
函数
,那么并不是PropTypes.function
,而是PropTypes.func
,因为function
是关键字
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
<div id="test1"></div>
<br>
<div id="test2"></div>
<br>
<div id="test3"></div>
<script type="text/babel">
class Demo extends React.Component {
render() {
// 这里我们之前打印过,有一个属性教props的对象
console.log("tcc", this.props);
const {name, age, sex} = this.props;
return (
<div>
<h2>姓名:{ name }</h2>
{ /* 展示的时候,性别+1 */ }
<h2>年龄:{ age+1 }</h2>
<h2>性别:{ sex }</h2>
</div>
)
}
}
// 验证属性的类型以及必填
Demo.propTypes = {
// React15.xxx版本的话,类型是直接使用React.PropTypes里面定义好的类型即可,但是React16.xxx版本,需要使用prop-types库
// name: React.PropTypes.string,
name: PropTypes.string.isRequired,
age: PropTypes.number,
}
// 在标签上写的所有属性,都会放到props对象中,传入除字符串以外其他类型的参数的话,外层需要使用{}包裹起来
ReactDOM.render(<Demo name="张三" age={18} sex="男"/>, document.getElementById('test1'))
ReactDOM.render(<Demo name={1} age={19} sex="女"/>, document.getElementById('test2'))
ReactDOM.render(<Demo age="21" sex="男"/>, document.getElementById('test3'))
</script>
</body>
</html>
效果
10.3.2、配置默认值
- 我们通过给组件身上添加
propTypes
属性,配置对props
的限制
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
<div id="test1"></div>
<br>
<div id="test2"></div>
<br>
<div id="test3"></div>
<script type="text/babel">
class Demo extends React.Component {
render() {
// 这里我们之前打印过,有一个属性教props的对象
console.log("tcc", this.props);
const {name, age, sex} = this.props;
return (
<div>
<h2>姓名:{ name }</h2>
{ /* 展示的时候,性别+1 */ }
<h2>年龄:{ age+1 }</h2>
<h2>性别:{ sex }</h2>
</div>
)
}
}
// 验证属性的类型以及必填
Demo.propTypes = {
// React15.xxx版本的话,类型是直接使用React.PropTypes里面定义好的类型即可,但是React16.xxx版本,需要使用prop-types库
// name: React.PropTypes.string,
name: PropTypes.string.isRequired,
age: PropTypes.number,
}
// 给props中的属性指定默认值
Demo.defaultProps = {
sex: '男'
}
// 在标签上写的所有属性,都会放到props对象中,传入除字符串以外其他类型的参数的话,外层需要使用{}包裹起来
ReactDOM.render(<Demo name="张三" age={18}/>, document.getElementById('test1'))
ReactDOM.render(<Demo name={1} age={19} sex="女"/>, document.getElementById('test2'))
ReactDOM.render(<Demo age="21"/>, document.getElementById('test3'))
</script>
</body>
</html>
效果
10.4、简化
10.4.1、简化props传递
- 上面组件案例,我们只列举了用户信息中的三个字段,如果要展示多个字段,那么我们要写很多属性,如果再多次使用组件,那么写起来会特别繁重
babel配合React
使我们可以在jsx
中使用...展开运算符
展开一个对象(只有在标签中使用才有用,其他地方使用打印为空或报错)
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
<div id="test1"></div>
<br>
<div id="test2"></div>
<br>
<div id="test3"></div>
<script type="text/babel">
class Demo extends React.Component {
render() {
// 这里我们之前打印过,有一个属性教props的对象
console.log("tcc", this.props);
const {name, age, sex} = this.props;
return (
<div>
<h2>姓名:{ name }</h2>
{ /* 展示的时候,性别+1 */ }
<h2>年龄:{ age+1 }</h2>
<h2>性别:{ sex }</h2>
</div>
)
}
}
// 验证属性的类型以及必填
Demo.propTypes = {
// React15.xxx版本的话,类型是直接使用React.PropTypes里面定义好的类型即可,但是React16.xxx版本,需要使用prop-types库
// name: React.PropTypes.string,
name: PropTypes.string.isRequired,
age: PropTypes.number,
}
// 给props中的属性指定默认值
Demo.defaultProps = {
sex: '男'
}
const u1 = {name: '张三', age: 18, sex: '男'}
const u2 = {name: '李四', age: 19, sex: '女'}
const u3 = {name: '王五', age: 21, sex: '男'}
// 这里的{...u1},并不是把u1对象解构出来放到的新创建的对象中,()在jsx中代表里面内容是js表达式,并不是对象的{}
ReactDOM.render(<Demo {...u1}/>, document.getElementById('test1'))
ReactDOM.render(<Demo {...u2}/>, document.getElementById('test2'))
ReactDOM.render(<Demo {...u3}/>, document.getElementById('test3'))
</script>
</body>
</html>
效果
10.4.2、简化配置写法
- 我们写的
Demo.propTypes、Demo.defaultProps
就是想直接把属性添加到Demo
对象上,其实在类内部我们也可以实现- 通过在类中使用
static
关键字,即可实现把属性添加到类上
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
<div id="test1"></div>
<br>
<div id="test2"></div>
<br>
<div id="test3"></div>
<script type="text/babel">
class Demo extends React.Component {
// 通过static关键字,将propTypes和defaultPorps两个属性放入Demo对象上
// 验证属性的类型以及必填
static propTypes = {
// React15.xxx版本的话,类型是直接使用React.PropTypes里面定义好的类型即可,但是React16.xxx版本,需要使用prop-types库
// name: React.PropTypes.string,
name: PropTypes.string.isRequired,
age: PropTypes.number,
}
// 给props中的属性指定默认值
static defaultProps = {
sex: '男'
}
render() {
// 这里我们之前打印过,有一个属性教props的对象
console.log("tcc", this.props);
const {name, age, sex} = this.props;
return (
<div>
<h2>姓名:{ name }</h2>
{ /* 展示的时候,性别+1 */ }
<h2>年龄:{ age+1 }</h2>
<h2>性别:{ sex }</h2>
</div>
)
}
}
const u1 = {name: '张三', age: 18}
const u2 = {name: 1, age: "19", sex: '女'}
const u3 = {age: 21}
// 这里的{...u1},并不是把u1对象解构出来放到的新创建的对象中,()在jsx中代表里面内容是js表达式,并不是对象的{}
ReactDOM.render(<Demo {...u1}/>, document.getElementById('test1'))
ReactDOM.render(<Demo {...u2}/>, document.getElementById('test2'))
ReactDOM.render(<Demo {...u3}/>, document.getElementById('test3'))
</script>
</body>
</html>
10.5、props与构造器的关系
- 我们打开官网,搜索
constructor
可以看到这么一段话
- 就是如果在
constructor
构造器中访问this.props
,那么必须要先调用super(props)
,否则可能获取到的为undefined
,具体要看React
的版本,我这里是直接控制台报错- 一般不会出现以上需求,因为你直接使用构造器参数中的
props
即可,没有必要访问this.props
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
<div id="test1"></div>
<br>
<div id="test2"></div>
<br>
<div id="test3"></div>
<script type="text/babel">
class Demo extends React.Component {
constructor(props) {
// 如果在调用 super(props); 前 使用this.props,那么this.props就是undefined
console.log("tccc1", this.props);
super(props);
console.log("tcc2", this.props);
}
render() {
const {name, age, sex} = this.props;
return (
<div>
<h2>姓名:{ name }</h2>
{ /* 展示的时候,性别+1 */ }
<h2>年龄:{ age+1 }</h2>
<h2>性别:{ sex }</h2>
</div>
)
}
}
const u1 = {name: '张三', age: 18, sex: '男'}
const u2 = {name: '李四', age: 19, sex: '女'}
const u3 = {name: '王五', age: 21, sex: '男'}
// 这里的{...u1},并不是把u1对象解构出来放到的新创建的对象中,()在jsx中代表里面内容是js表达式,并不是对象的{}
ReactDOM.render(<Demo {...u1}/>, document.getElementById('test1'))
ReactDOM.render(<Demo {...u2}/>, document.getElementById('test2'))
ReactDOM.render(<Demo {...u3}/>, document.getElementById('test3'))
</script>
</body>
</html>
效果
10.6、函数式组件的props
- 虽然函数不能玩
refs、state
两个属性,但是可以使用props
,因为函数可以接收参数- 所有传入函数式组件的
props
,都会被封装成一个对象传入组件函数的参数中
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
<div id="test1"></div>
<script type="text/babel">
function Demo(props) {
console.log("tcc", props);
const {name, age, sex} = props;
return (
<div>
<h2>姓名:{ name }</h2>
{ /* 展示的时候,性别+1 */ }
<h2>年龄:{ age+1 }</h2>
<h2>性别:{ sex }</h2>
</div>
)
}
const u1 = {name: '张三', age: 18, sex: '男'}
// 这里的{...u1},并不是把u1对象解构出来放到的新创建的对象中,()在jsx中代表里面内容是js表达式,并不是对象的{}
ReactDOM.render(<Demo {...u1}/>, document.getElementById('test1'))
</script>
</body>
</html>
效果
11、组件三大核心之三:refs
11.1、使用
- 通过在
dom
上定义ref
属性,React
即会把标签上含有ref
属性的元素放入类自身的refs
对象中,在其他地方编写this.refs.xxxx
即可获取dom
标签元素- 以下案例:输入框1输入内容后,点击右侧按钮,弹出输入的内容;输入框2输入内容失去焦点后,弹出输入的内容
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
<div id="test1"></div>
<script type="text/babel">
class Demo extends React.Component {
render() {
return (
<div>
{/* 通过在标签上定义 ref属性,这个dom就会被放到this.refs.input1中 */ }
<input ref="input1" type="text" placeholder="请输入" />
<button onClick={this.handleAlertVal}>点击弹出输入内容</button>
<input ref="input2" type="text" placeholder="请输入" onBlur={this.handleAlertVal2} />
</div>
)
}
handleAlertVal = () => {
// 读取react放入到refs对象中的input1元素
console.log("tcc", this.refs);
alert(this.refs.input1.value)
}
handleAlertVal2 = (event) => {
// 这里由于是在input标签上帮的方法,所以event.target就是input元素,当然也可以使用this.refs.input2获取
alert(event.target.value)
}
}
ReactDOM.render(<Demo />, document.getElementById('test1'))
</script>
</body>
</html>
效果
11.2、字符串形式的ref
- 上面我们使用的就是字符串形式的
ref="xxxx"
,但是官网说使用这种ref
有性能的问题
- 下面我们将会使用到其他形式的ref
11.3、回调形式的ref
- 通过给标签上
ref
的属性值上绑定函数
,即是回调形式的ref
- 使用回调形式的ref,会在执行render方法的时候,发现标签上有ref属性,并且值是一个函数就会立即帮你执行一次函数并且把当前这个标签的dom节点作为参数传递给这个函数
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
<div id="test1"></div>
<script type="text/babel">
class Demo extends React.Component {
render() {
return (
<div>
{/* 使用回调形式的ref,会在执行render方法的时候,发现标签上有ref属性,并且值是一个函数,
就会立即帮你执行一次函数
并且把当前这个标签的dom节点作为参数传递给这个函数 */ }
<input ref={(currentNode) => this.input1 = currentNode} type="text" placeholder="请输入" />
<button onClick={this.handleAlertVal}>点击弹出输入内容</button>
<input ref={(currentNode) => this.input2 = currentNode} type="text" placeholder="请输入" onBlur={this.handleAlertVal2} />
</div>
)
}
handleAlertVal = () => {
console.log(this);
alert(this.input1.value)
}
handleAlertVal2 = () => {
alert(this.input2.value)
}
}
ReactDOM.render(<Demo />, document.getElementById('test1'))
</script>
</body>
</html>
效果
11.3.1、关于回调 refs 的说明
- 如果
ref
的值是内联函数,那么在每次重新渲染(首次渲染没问题)的时候,都会调用两次这个函数,一次参数是null
,第二次参数才是真正的dom
。如果定义成class
的绑定函数的方式可以避免上述问题- 因为在执行
redner
的时候,React
不知道你绑定的函数是不是之前的函数了,所以会先给你一个null,清空之前的数据,再把真实的dom
给你- 如果定义成
class
的绑定函数方式,那么React
就知道你这次绑定的函数肯定和上次绑定的函数一样,所以就没有任何问题了,更新的话一次也不会帮你调用了
- 其实一般情况下没有什么问题,所以想怎么写怎么写
- 下面演示一下
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
<div id="test1"></div>
<script type="text/babel">
class Demo extends React.Component {
state = {
isLucky: false
}
render() {
return (
<div>
<h2>{this.state.isLucky ? "恭喜你,中奖了" : "很遗憾,未中奖"}</h2>
<button onClick={() => {
this.setState({
isLucky: !this.state.isLucky
})
}}>点我改变运气</button>
{/* class绑定函数,只有在首次渲染时帮你调用一次 */ }
<input ref={this.bindInput1} type="text" placeholder="请输入" />
<button onClick={this.handleAlertVal}>点击弹出输入内容</button>
{/* 内联函数,再重新渲染时,react会帮你调用两次 */}
<input ref={(currentNode) => {this.input2 = currentNode;console.log("tcc2", currentNode)} } type="text" placeholder="请输入" onBlur={this.handleAlertVal2} />
</div>
)
}
// class绑定函数,依旧可以接收到currentNode
bindInput1 = (currentNode) => {
console.log("tcc1", currentNode);
this.input1 = currentNode
}
handleAlertVal = () => {
alert(this.input1.value)
}
handleAlertVal2 = () => {
alert(this.input2.value)
}
}
ReactDOM.render(<Demo />, document.getElementById('test1'))
</script>
</body>
</html>
效果
11.4、createRef API
- 最后一种方式时使用
React
提供的一个React.createRef API
,来创建一个容器,然后在ref中把这个容器绑定上,React
就能自动把标签放入容器的current
属性中- 需要先使用
React.creatRef
定义容器,然后编写this.xxx.current
来获取容器
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
<div id="test1"></div>
<script type="text/babel">
class Demo extends React.Component {
input1 = React.createRef();
input2 = React.createRef();
render() {
return (
<div>
{/* 把容器交给ref */ }
<input ref={this.input1} type="text" placeholder="请输入" />
<button onClick={this.handleAlertVal}>点击弹出输入内容</button>
<input ref={this.input2 } type="text" placeholder="请输入" onBlur={this.handleAlertVal2} />
</div>
)
}
handleAlertVal = () => {
// 在使用的时候,要使用 .current 才能获取到容器
console.log("tcc1", this.input1);
alert(this.input1.current.value)
}
handleAlertVal2 = () => {
console.log("tcc1", this.input2);
alert(this.input2.current.value)
}
}
ReactDOM.render(<Demo />, document.getElementById('test1'))
</script>
</body>
</html>
效果
12、数据动态绑定
- 页面上需要表单数据的地方有很多,当我们点登录的时候,要把用户填写的所有信息都提交到后台,难道这时候我们要一个表单一个表单,通过
ref
获取表单数据吗,不可能- 我们可以在用户修改数据的时候,把用户改完的数据绑定到
state
中,这样我们点击提交的时候,就可以直接从state
中获取数据即可
12.1、使用
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
<div id="test1"></div>
<script type="text/babel">
class Demo extends React.Component {
state = {
username: null,
password: null
}
render() {
return (
<div>
账号:<input onChange={this.usernameChange} type="text" placeholder="请输入" />
密码:<input onChange={this.passwordChange} type="text" placeholder="请输入" />
<button onClick={this.login}>登录</button>
</div>
)
}
usernameChange = (e) => {
this.setState({
username: e.target.value
})
}
passwordChange = (e) => {
this.setState({
password: e.target.value
})
}
login = () => {
console.log("登录信息:", this.state);
}
}
ReactDOM.render(<Demo />, document.getElementById('test1'))
</script>
</body>
</html>
效果
我们观察之前下载的
react
插件也可观察出来
12.2、优化
- 上面每个表单,我们都要写一个方法,去修改对应字段,其实我们可以使用如下方法即可解决以上问题
- 使用高阶函数(函数参数是一个对象,或者,函数返回一个对象),比如:柯里化
写法一:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
<div id="test1"></div>
<script type="text/babel">
class Demo extends React.Component {
state = {
username: null,
password: null
}
render() {
return (
<div>
{ /* 这里直接调用 handleFormChange 函数,并把要修改的字段告诉他 */ }
账号:<input onChange={this.handleFormChange('username')} type="text" placeholder="请输入" />
密码:<input onChange={this.handleFormChange('password')} type="text" placeholder="请输入" />
<button onClick={this.login}>登录</button>
</div>
)
}
// 这里返回值是一个函数,供 onChange 使用,并且在调用的时候,依旧会传入参数 e
handleFormChange = (field) => {
return (e) => {
this.setState({
[field]: e.target.value
})
}
}
login = () => {
console.log("登录信息:", this.state);
}
}
ReactDOM.render(<Demo />, document.getElementById('test1'))
</script>
</body>
</html>
写法二:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
<div id="test1"></div>
<script type="text/babel">
class Demo extends React.Component {
state = {
username: null,
password: null
}
render() {
return (
<div>
{ /* 这里直接传入一个函数,函数中再调用handleFormChange,并且参数在传入中定义即可 */ }
账号:<input onChange={(e) => this.handleFormChange(e, 'username')} type="text" placeholder="请输入" />
密码:<input onChange={e => this.handleFormChange(e, 'password')} type="text" placeholder="请输入" />
<button onClick={this.login}>登录</button>
</div>
)
}
// 这里直接使用两个参数即可
handleFormChange = (e, field) => {
this.setState({
[field]: e.target.value
})
}
login = () => {
console.log("登录信息:", this.state);
}
}
ReactDOM.render(<Demo />, document.getElementById('test1'))
</script>
</body>
</html>
13、生命周期旧(v16重要)
理解
- 组件从创建到死亡它会经经历一些特定的阶段
React
组件中包含一系列钩子函数(生命周期回调函数),会在特定的时刻调用- 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作
- 钩子函数执行顺序与编写顺序无关!!!!
旧生命周期指v16版本以前的生命周期
生命周期流程图
虽然看上面钩子函数有很多,但其实常用的只有几个
render
:每次state
中数据修改时,React
会帮咱调用,用于重新渲染页面componentDidMount
:组件挂载好后会自动调用,在这里面可以写页面初始化的一些数据获取等逻辑componentWillUnmount
:组件将要卸载时自动调用,在这里可以清空一些定时器等操作总结所有钩子函数调用时机,由于时机不同,以下不按照调用顺序解释
constructor
:挂载时第一个调用,初始化state
等使用,一般无需使用
componentWillMount
,组件准备挂载时调用,一般不用*
render
:当数据修改时,React
会自动调用渲染页面*
componentDidMount
:组件挂载完毕,一般获取页面初始化数据等操作在这里执行
componentWillReceiveProps
:父组件调用render
后,子组件会自动调用,并且会把props
传入其中
shouldComponentUpdate
:调用setState()
方法后会自动调用
- 如果返回
true
,那么正常修改值,并且render()
- 如果返回
false
,那么反之不会修改值,并且不调用render()
- 如果不写该函数,那么
React
会自动补全这个函数,并且返回true
componentWillUpdate
:组件初始化调用render
后,再次调用render
前会调用的钩子函数
componentDidUpdate
:组件更新完毕后的钩子函数*
componentWillUnmount
:组件卸载前的钩子函数,可以清空定时器等结束操作
13.1、挂载时流程
- 挂载时,组件会以此自动调用如下方法
constructor、componentWillMount、render、componentDidMount
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
<div id="test1"></div>
<script type="text/babel">
class Demo extends React.Component {
constructor() {
super();
console.log("constructor:构造器函数");
}
componentWillMount() {
console.log("componentWillMount:组件将要挂载");
}
componentDidMount() {
console.log("componentDidMount:组件挂载完毕");
}
render() {
console.log("render:渲染组件");
return (
<div>
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById('test1'))
</script>
</body>
</html>
效果
13.2、父组件render
流程
- 这里我们涉及到父组件,不过父子组件编写并不难,仔细观察如下案例即可
- 父组件调用
render
后,子组件会按照如下流程依次执行:componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render、componentDidUpdate
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
<div id="test1"></div>
<script type="text/babel">
// 父组件
class Demo extends React.Component {
state = {
isLucky: true
}
render() {
console.log("render:渲染组件");
return (
<div>
{/* 直接在这里使用标签形式的子组件即可,传入isLucky属性,子组件通过props来获取 */}
<Demo2 isLucky={this.state.isLucky} />
<button onClick={() => {
this.setState({
isLucky: !this.state.isLucky
})
}}>抽奖</button>
</div>
)
}
}
// 子组件
class Demo2 extends React.Component {
componentWillReceiveProps(nextProps) {
console.log("componentWillReceiveProps:组件收到新的属性", nextProps);
}
constructor() {
super();
console.log("constructor:构造器函数");
}
componentWillMount() {
console.log("componentWillMount:组件将要挂载");
}
componentDidMount() {
console.log("componentDidMount:组件挂载完毕");
}
shouldComponentUpdate() {
console.log("shouldComponentUpdate:组件是否需要更新");
return true;
}
componentWillUpdate() {
console.log("componentWillUpdate:组件将要更新");
}
componentDidUpdate() {
console.log("componentDidUpdate:组件更新完毕");
}
render() {
console.log("render:渲染组件");
return (
<div>
<div>{this.props.isLucky ? '恭喜你,中奖了' : '很遗憾,未中奖'}</div>
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById('test1'))
</script>
</body>
</html>
效果
13.3、setState()
方法调用后流程
shouldComponentUpdate()
方法,如果返回true
,那么就会正常修改值,调用render
方法。如果返回false
,那么旧不会修改值,也就不会引起页面修改。如果不写的话,React
会自动补全这个方法,并且返回true
- 当组件调用
setState()
修改值以后,组件会按照如下流程依次执行:shouldComponentUpdate、componentWillUpdate、render、componentDidUpdate
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
<div id="test1"></div>
<script type="text/babel">
class Demo extends React.Component {
// 返回true,正常修改。false,停止修改
shouldComponentUpdate() {
console.log("shouldComponentUpdate:组件是否需要更新");
return true;
}
componentWillUpdate() {
console.log("componentWillUpdate:组件将要更新");
}
componentDidUpdate() {
console.log("componentDidUpdate:组件更新完毕");
}
state = {
isLucky: true
}
render() {
console.log("render:渲染组件");
return (
<div>
<div>{this.state.isLucky ? '恭喜你,中奖了' : '很遗憾,未中奖'}</div>
<button onClick={() => {
this.setState({
isLucky: !this.state.isLucky
})
}}>抽奖</button>
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById('test1'))
</script>
</body>
</html>
效果
13.4、forceUpdate()
方法调用后流程
- 这个函数用于强制使页面刷新,在继承类
React.Component.Prototype
中,本身直接由于继承,直接使用this.forceUpdate()
调用即可,一般没必要调用!!- 调用这个方法后,会依次执行如下函数:
componentWillUpdate、render、componentDidUpdate
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
<div id="test1"></div>
<script type="text/babel">
class Demo extends React.Component {
componentWillUpdate() {
console.log("componentWillUpdate:组件将要更新");
}
componentDidUpdate() {
console.log("componentDidUpdate:组件更新完毕");
}
render() {
console.log("render:渲染组件", new React.Component());
return (
<div>
<button onClick={() => {
this.forceUpdate()
}}>强制刷新</button>
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById('test1'))
</script>
</body>
</html>
效果
13.5、卸载时流程
- 卸载时,
React
只会调用一个方法componentWillUnmount
- 我们调用
ReactDOM.unmountComponentAtNode()
,方法,参数里面传入咱们挂载时的节点,即可卸载挂载到上面的组件,对应的,节点内的所有内容消失
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
<div id="test1"></div>
<script type="text/babel">
class Demo extends React.Component {
componentWillUnmount() {
console.log("componentWillUnmount:组件将要卸载");
}
render() {
console.log("render:渲染组件");
return (
<div>
<button onClick={() => {
ReactDOM.unmountComponentAtNode(document.getElementById('test1'))
}}>卸载组件</button>
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById('test1'))
</script>
</body>
</html>
效果
14、生命周期新(v17)
- 以下生命周期钩子函数适用于
v17
以上的版本,虽然使用以前的钩子函数依旧可以正常执行,但是某几个钩子执行控制台会有警告- 以下案例,我会修改上面使用的
React
版本为v17.0.1
- 生命周期钩子流程图
- 对比
旧图
,可以发现少了componentWillMount、componentWillReceiveProps、componentWillUpdate
,多了两个getDerivedStateFromProps、getSnapshotBeforeUpdate
两个钩子,React更新DOM和refs
这个其实旧版本也有这个操作,只不过图上没有表示出来,自己也无法控制,所以暂且不提。- 第一条说有几个钩子会有警告,其实是3条,就是上面图上“少了”的3个钩子(其实也不是少了,改名了),下面会详细讲解原因
14.1、改名的三个钩子
- 改名的是上面图中缺少的三个钩子
componentWillMount -> UNSAFE_componentWillMount、componentWillReceiveProps -> UNSAFE_componentWillReceiveProps、componentWillUpdate -> UNSAFE_componentWillUpdate
- 为此,我特意写了如下组件验证原来钩子,可见控制台有如下三个警告,并且修改方法也写明了
以前写法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/v17/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/v17/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
<div id="test1"></div>
<script type="text/babel">
// 父组件
class Demo extends React.Component {
state = {
parentLuckeyNum: 0
}
render() {
return (
<div>
<Demo2 parentLuckeyNum={this.state.parentLuckeyNum} />
<button onClick={() => {
this.setState({
parentLuckeyNum: parseInt(Math.random() * 10)
})
}}>点击更改父亲的幸运数</button>
</div>
)
}
}
// 子组件
class Demo2 extends React.Component {
constructor() {
super();
console.log("constructor:构造器函数");
}
componentWillMount() {
console.log("componentWillMount:组件将要挂载");
}
componentDidMount() {
console.log("componentDidMount:组件挂载完毕");
}
shouldComponentUpdate() {
console.log("shouldComponentUpdate:组件是否需要更新");
return true;
}
componentWillUpdate() {
console.log("componentWillUpdate:组件将要更新");
}
componentDidUpdate() {
console.log("componentDidUpdate:组件更新完毕");
}
componentWillUnmount() {
console.log("componentWillUnmount:组件将要卸载");
}
componentWillReceiveProps(nextProps) {
console.log("componentWillReceiveProps:组件将要接受新的属性");
}
state = {
myLuckeyNum: 0
}
render() {
const { parentLuckeyNum } = this.props
const { myLuckeyNum } = this.state
return (
<div>
<h2>父亲的幸运数:{parentLuckeyNum}</h2>
<h2>我的幸运数:{myLuckeyNum}</h2>
<button onClick={() => {
this.setState({
myLuckeyNum: parseInt(Math.random() * 10)
})
}}>点击更改我的幸运数</button>
<button onClick={() => {
ReactDOM.unmountComponentAtNode(document.getElementById('test1'))
}}>点击卸载组件</button>
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById('test1'))
</script>
</body>
</html>
效果
-
查看钩子是否正常执行
-
查看警告控制台
新写法
- 我们只要按照上面提示,修改这三个钩子方法的名字即可
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/v17/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/v17/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
<div id="test1"></div>
<script type="text/babel">
// 父组件
class Demo extends React.Component {
state = {
parentLuckeyNum: 0
}
render() {
return (
<div>
<Demo2 parentLuckeyNum={this.state.parentLuckeyNum} />
<button onClick={() => {
this.setState({
parentLuckeyNum: parseInt(Math.random() * 10)
})
}}>点击更改父亲的幸运数</button>
</div>
)
}
}
// 子组件
class Demo2 extends React.Component {
constructor() {
super();
console.log("constructor:构造器函数");
}
UNSAFE_componentWillMount() {
console.log("componentWillMount:组件将要挂载");
}
componentDidMount() {
console.log("componentDidMount:组件挂载完毕");
}
shouldComponentUpdate() {
console.log("shouldComponentUpdate:组件是否需要更新");
return true;
}
UNSAFE_componentWillUpdate() {
console.log("componentWillUpdate:组件将要更新");
}
componentDidUpdate() {
console.log("componentDidUpdate:组件更新完毕");
}
componentWillUnmount() {
console.log("componentWillUnmount:组件将要卸载");
}
UNSAFE_componentWillReceiveProps(nextProps) {
console.log("componentWillReceiveProps:组件将要接受新的属性");
}
state = {
myLuckeyNum: 0
}
render() {
const { parentLuckeyNum } = this.props
const { myLuckeyNum } = this.state
return (
<div>
<h2>父亲的幸运数:{parentLuckeyNum}</h2>
<h2>我的幸运数:{myLuckeyNum}</h2>
<button onClick={() => {
this.setState({
myLuckeyNum: parseInt(Math.random() * 10)
})
}}>点击更改我的幸运数</button>
<button onClick={() => {
ReactDOM.unmountComponentAtNode(document.getElementById('test1'))
}}>点击卸载组件</button>
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById('test1'))
</script>
</body>
</html>
14.2、为什么改名
- 其实这是
React
在为未来谋划,未来使用异步渲染
的时候,他觉得这几个钩子可能会引发问题,觉得我们经常会滥用它们,就是在不合适的时候使用,并且在未来版本有可能直接不兼容
14.3、getDerivedStateFromProps
- 该钩子函数和其他方法使用不同,是一个
静态方法
- 该方法必须返回一个对象,或者
null
- 返回对象的时候,他会和组件本身
state
进行合并操作,并且被合并的return对象
中的数据无法被修改- 返回
null
的时候,他则无任何修改state
操作- 使用这个钩子函数的时候,不会调用以上三个可能会有问题的钩子了
- 该方法会接收两个参数
props、state
,每当props或者state
修改后,都会执行这个钩子函数- 这个钩子函数主要用于组件
state
里面的值全部由props
来决定,并且不可更改
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/v17/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/v17/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
<div id="test1"></div>
<script type="text/babel">
// 父组件
class Demo extends React.Component {
state = {
parentLuckeyNum: 0
}
render() {
return (
<div>
<Demo2 parentLuckeyNum={this.state.parentLuckeyNum} myLuckeyNum={666} />
<button onClick={() => {
this.setState({
parentLuckeyNum: parseInt(Math.random() * 10)
})
}}>点击更改父亲的幸运数</button>
</div>
)
}
}
// 子组件
class Demo2 extends React.Component {
constructor() {
super();
console.log("constructor:构造器函数");
}
// 直接将父组件的 props,作为子组件本身的state使用
static getDerivedStateFromProps(props, state) {
console.log("getDerivedStateFromProps:获取最新的属性", props, state);
return props;
}
componentDidMount() {
console.log("componentDidMount:组件挂载完毕");
}
shouldComponentUpdate() {
console.log("shouldComponentUpdate:组件是否需要更新");
return true;
}
componentDidUpdate() {
console.log("componentDidUpdate:组件更新完毕");
}
componentWillUnmount() {
console.log("componentWillUnmount:组件将要卸载");
}
state = {
myLuckeyNum: 0
}
render() {
const { parentLuckeyNum } = this.props
const { myLuckeyNum } = this.state
return (
<div>
<h2>父亲的幸运数:{parentLuckeyNum}</h2>
<h2>我的幸运数:{myLuckeyNum}</h2>
<button onClick={() => {
this.setState({
myLuckeyNum: parseInt(Math.random() * 10)
})
}}>点击更改我的幸运数</button>
<button onClick={() => {
ReactDOM.unmountComponentAtNode(document.getElementById('test1'))
}}>点击卸载组件</button>
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById('test1'))
</script>
</body>
</html>
效果
14.4、getSnapshotBeforeUpdate
- 该生命周期会接收两个参数:
prevProps、prevState
,更新前的props和state
- 并且可以返回一个值,将作为
componentDidUpdate
钩子函数的第三个此参数传入- 此生命周期通常不需要,但在重新渲染期间手动保留滚动位置等情况下可能很有用
- 下面我编写一个滚动的案例,滚动后定格在当前滚动的高度
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/v17/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/v17/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
<div id="test1"></div>
<script type="text/babel">
// 父组件
class Demo extends React.Component {
getSnapshotBeforeUpdate(prevProps, prevState) {
// 这里返回每次新增用户前,ul的滚动条离顶部的距离
return document.querySelector(".userList").scrollHeight
}
componentDidUpdate(prevProps, prevState, snapshot) {
// 这里snapshot能获取到上面return的结果,然后指定ul滚动条距离顶部的距离为 当前dom滚动条的高度 += dom新增完后ul的高度 - dom新增前ul的高度 就可以保持滚动条一直处于不动状态
const dom = document.querySelector(".userList")
dom.scrollTop += dom.scrollHeight - snapshot
}
componentDidMount() {
// 定时新增用户
setInterval(() => {
// 注意,是unshift,让元素从上面新增
this.state.userList.unshift(`用户${this.state.index}`)
this.setState({
userList: this.state.userList,
index: this.state.index + 1
})
}, 1000);
}
state = {
userList: [],
index: 0
}
render() {
// 这里渲染的元素必须由key属性,这里暂且使用index代替
return (
<div>
<ul className="userList">
{this.state.userList.map((item, index) => {
return <li key={index} className="userList-item">{item}</li>
})}
</ul>
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById('test1'))
</script>
<style>
.userList {
width: 100px;
height: 130px;
overflow: auto;
background-color: cadetblue;
}
.userList-item {
height: 30px;
}
</style>
</body>
</html>
效果
15、key
- 上面我们在遍历数据然后使用
map
返回jsx
的时候,都会在返回的jsx
上面添加一个属性key
,不然控制台就会有报错,那么为什么要加这个呢React
在每次执行render
方法的时候,不是简单的把return
里面的东西,直接放到上面,然后等待babel
渲染,而是会先变成虚拟DOM,然后再经过一个diff算法,最后把算法结果放到上面- 在
diff
算法中,会获取到原来的虚拟DOM树,然后和新的虚拟DOM树比较,比较的时候,就会先拿着相同key
的元素比较,如果一致,那么则原来的dom
不动;如果不一致,那么就重新渲染这一块的dom- 一般key会使用如下两种方式赋值
- 使用
index
:这种方式用于遍历的dom
不会改变的时候,即被遍历的集合不会被修改了,这样的话,使用index
是没问题的,可以暂用。- 使用
数据唯一标识符
:比如遍历一个对象集合,一般都会使用对象的id
属性,如果没有,那么其他唯一值也是可以的,因为这些数据是经历后台查询出来的,有增删改的,会改变集合的,那么再使用index
的话,之前元素上的index
和之后的index
对不上,实际上内容并没有被改变,但是因为参照物上的index
变了,所以完全对不上了,就会重新被渲染,,就会导致性能变差,或渲染错乱!!
15.1、虚拟DOM
虚拟 DOM(Virtual DOM)在现代前端开发中具有重要的用处,主要体现在以下几个方面:
一、提高性能
- 减少直接操作真实 DOM 的开销
- 在浏览器中,操作真实 DOM 是相对昂贵的操作,因为它会触发浏览器的重排(reflow)和重绘(repaint)。虚拟 DOM 则是在内存中以 JavaScript 对象的形式存在,对其进行操作不会直接引起浏览器的重排和重绘,从而大大减少了性能开销。
- 例如,当你需要频繁更新页面上的一部分内容时,如果直接操作真实 DOM,可能会导致整个页面的重绘和重排,影响性能。而使用虚拟 DOM,可以先在内存中对虚拟 DOM 进行修改,然后一次性地将变化更新到真实 DOM 上,减少了不必要的重绘和重排次数。
- 高效的 DOM diff 算法
- 虚拟 DOM 可以通过高效的 DOM diff 算法来计算出真实 DOM 中需要更新的最小部分。这个算法会比较新旧两个虚拟 DOM 树的差异,只对发生变化的部分进行实际的 DOM 操作。
- 比如,当一个列表中的某一项数据发生变化时,DOM diff 算法可以精确地找到这个变化的项,并只更新对应的真实 DOM 元素,而不是重新渲染整个列表。
二、提升开发效率
- 跨平台渲染
- 虚拟 DOM 可以实现跨平台渲染,使得同一份代码可以在不同的环境中运行,如浏览器、移动端、服务器端等。这是因为虚拟 DOM 是一个抽象的概念,不依赖于特定的平台。
- 例如,使用 React Native 可以利用虚拟 DOM 的原理在移动端进行开发,实现一次编写,多平台运行,提高了开发效率。
- 组件化开发
- 在组件化开发中,虚拟 DOM 使得组件的更新更加高效和可控。每个组件可以维护自己的虚拟 DOM 树,当组件的状态发生变化时,只需要更新组件对应的虚拟 DOM 部分,而不会影响到整个页面。
- 例如,在一个大型的单页应用中,有很多个组件,每个组件都可以独立地进行开发和维护。当某个组件的状态发生变化时,虚拟 DOM 可以确保只更新该组件在页面上的部分,而不会影响其他组件的显示。
三、增强可维护性
- 清晰的代码结构
- 使用虚拟 DOM 可以使代码结构更加清晰,易于理解和维护。通过将页面表示为虚拟 DOM 树,开发者可以更直观地看到页面的结构和组成部分。
- 例如,在使用 React 开发时,组件的结构和状态都通过虚拟 DOM 来表示,开发者可以很容易地理解组件的工作原理和更新机制。
- 方便的调试和测试
- 虚拟 DOM 使得调试和测试更加方便。由于虚拟 DOM 是在内存中存在的,开发者可以在不影响真实 DOM 的情况下进行调试和测试,更容易定位问题。
- 例如,在进行单元测试时,可以使用虚拟 DOM 来模拟真实 DOM 的行为,从而更方便地测试组件的功能和性能。
15.2、diff算法
Diff 算法,即差异比较算法,在前端开发中尤其是在使用虚拟 DOM 的框架(如 React、Vue 等)中起着至关重要的作用。
一、Diff 算法的作用
- 高效更新真实 DOM
- 在前端应用中,当数据发生变化时,需要更新页面以反映这些变化。如果直接对真实 DOM 进行操作,每次更新都可能导致浏览器进行重排和重绘,这是非常耗时的操作。Diff 算法通过比较新旧虚拟 DOM 树的差异,只对发生变化的部分进行实际的 DOM 操作,从而大大提高了更新效率。
- 例如,当一个列表中的某一项数据发生变化时,Diff 算法可以精确地找到这个变化的项,并只更新对应的真实 DOM 元素,而不是重新渲染整个列表。
- 最小化 DOM 操作次数
- Diff 算法的目标是找到最小的操作集合,以将旧的虚拟 DOM 转换为新的虚拟 DOM。这样可以减少对真实 DOM 的操作次数,提高性能。
- 比如,在一个复杂的页面中,有多个组件同时发生变化,Diff 算法会分析这些变化,并确定最有效的方式来更新真实 DOM,避免不必要的操作。
二、Diff 算法的工作原理
- 树的比较
- Diff 算法首先会对新旧两棵虚拟 DOM 树进行比较。它会从树的根节点开始,逐个比较节点的类型、属性和子节点。
- 如果节点类型不同,那么旧节点将被替换为新节点。如果节点类型相同,那么继续比较节点的属性和子节点。
- 节点的比较
- 对于相同类型的节点,Diff 算法会比较它们的属性。如果属性发生了变化,那么对应的真实 DOM 元素的属性将被更新。
- 例如,如果一个按钮的文本从 “点击我” 变为 “按下我”,Diff 算法会检测到这个变化,并更新真实 DOM 中按钮的文本内容。
- 子节点的比较
- Diff 算法会比较节点的子节点列表。它会采用一些优化策略来提高比较效率,比如同层比较和 key 属性的使用。
- 同层比较是指只比较同一层级的子节点,而不会跨层级比较。这样可以减少比较的次数,提高性能。
- key 属性是一种给子节点添加唯一标识的方式。当子节点具有 key 属性时,Diff 算法可以更准确地识别哪些子节点发生了变化,哪些子节点是新添加的或被删除的。
三、Diff 算法的优势
- 性能优化
- 通过最小化 DOM 操作次数,Diff 算法可以显著提高前端应用的性能。特别是在处理大型应用和频繁更新的场景下,Diff 算法的优势更加明显。
- 例如,在一个数据密集型的应用中,Diff 算法可以快速地更新页面,而不会导致页面卡顿或响应缓慢。
- 可维护性
- Diff 算法使得前端代码更加易于维护。由于它可以自动处理页面的更新,开发者只需要关注数据的变化和虚拟 DOM 的构建,而不需要手动操作真实 DOM。
- 例如,在一个复杂的单页应用中,有很多个组件和状态需要管理。Diff 算法可以帮助开发者更轻松地处理这些变化,提高代码的可维护性。
总之,Diff 算法是前端开发中非常重要的一种技术,它通过高效地比较新旧虚拟 DOM 树的差异,实现了对真实 DOM 的最小化更新,提高了前端应用的性能和可维护性。
15.3、使用index和key
- 注意观察左侧使用
index
会导致什么问题
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- react 核心库,引入他,就有了全局的React对象 -->
<script type="text/javascript" src="../js/v17/react.development.js"></script>
<!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
<script type="text/javascript" src="../js/v17/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
<div id="test1"></div>
<script type="text/babel">
// 父组件
class Demo extends React.Component {
state = {
list1: [
{
id: 1,
name: '张三'
},
{
id: 2,
name: '李四'
},
{
id: 3,
name: '王五'
}
]
}
render() {
// 这里渲染的元素必须由key属性,这里暂且使用index代替
return (
<div style={{display: 'flex', gap: '40px'}}>
<button onClick={this.addUser}>添加用户</button>
<div>
<h3>使用index</h3>
<ul>
{this.state.list1.map((item, index) => {
return <li key={index} className="userList-item">{item.name + '几岁了:'} <input /></li>
})}
</ul>
</div>
<div>
<h3>使用id</h3>
<ul>
{this.state.list1.map((item, index) => {
return <li key={item.id} className="userList-item">{item.name + '几岁了:'} <input /></li>
})}
</ul>
</div>
</div>
)
}
addUser = () => {
this.setState({
list1: [
{
id: Math.random(),
name: '赵六'
},
...this.state.list1,
]
})
}
}
ReactDOM.render(<Demo />, document.getElementById('test1'))
</script>
<style>
.userList {
width: 100px;
height: 130px;
overflow: auto;
background-color: cadetblue;
}
.userList-item {
height: 30px;
}
</style>
</body>
</html>
效果
使用脚手架
- 以上使用原生
html
编写过于麻烦,还要自己引入react、react-dom、babel
等第三方库,并且没有规范化,可以随意写。并且渲染效率极低,现用现编译。- 有时候项目大的话,启动的时候,编译就会很慢,导致页面白屏现象发生
- 脚手架就像盖楼外面的铁架子一样,帮我们把基本框架搭好,用到的第三方依赖库先下好,打包配置等都先把基本的给我们完成,我们只需在上面改改,加加我们自己的东西即可,也是一个规范化约束的工具,也可以在启动的时候先编译好我们的代码,提高效率!
vue
的脚手架有vue-cli、vite
等,而React
的脚手架有create-react-app
- 征募整体技术架构为
react + webpack + es6 + eslint
1、安装并启动
- 安装一般使用
npm
来安装,如果还不懂npm
,那么先查看:Node-包管理工具整套下载使用讲解(nvm、npm、yarn、cnpm、pnpm、nrm)- 命令如下
npm install -g create-react-app
// 或
yarn global add create-react-app
2、使用脚手架创建项目
- 进入到项目存放地址,然后在地址栏输入
cmd
,最后执行如下命令即可,第一次肯定会很慢- 如果创建失败,可以试试更换镜像地址
npm config set registry https://registry.npmmirror.com
create-react-app my-react
3、进入项目文件夹
- 进入到项目根文件夹中
cd xxx/xxx/xxx
4、启动项目
npm start
5、项目目录讲解
- 项目目录大概分为如下结构
noede_modules
:项目所需第三方依赖public:
静态资源文件夹
favicon.icon:
网站页签图标index.html:
主页面logo192.png:
logo图logo512.png:
logo图manifest.json:
应用加壳的配置文件robots.txt:
爬虫协议文件src:
源码文件夹
App.css:
App组件的样式App.js:
App组件App.test.js:
用于给App做测试index.css:
全局样式index.js:
入口文件logo.svg:
logo图reportWebVitals.js:
页面性能分析文件(需要web-vitals
库的支持)setupTests.js:
组件单元测试的文件(需要jest-dom
库的支持).gitignore:
git忽略文件package-lock.json:
npm缓存依赖文件package.json:
项目配置文件以及依赖版本文件README.md:
项目简介
5.1、主页面public->index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<!-- %PUBLIC_URL%代表public文件夹的路径 -->
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<!-- 开启理想视口,用于做移动端网页的适配 -->
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- 用于配置浏览器页签+地址栏的颜色(仅支持安卓手机浏览器) -->
<meta name="theme-color" content="#000000" />
<!-- 网页描述 -->
<meta
name="description"
content="Web site created using create-react-app"
/>
<!-- 应用加壳时的配置文件 -->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
</head>
<body>
<!-- 若浏览器不支持js则展示标签中的内容 -->
<noscript>You need to enable JavaScript to run this app.</noscript>
<!-- 根元素,用用于把组件放到这里来 -->
<div id="root"></div>
</body>
</html>
5.2、入口文件src->index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
// 全局样式
import './index.css';
// APP组件
import App from './App';
// import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
// 一下标签是校验react写法的,可以删除,如果写法不正确,会在控制台提示警告等信息
<React.StrictMode>
<App />
</React.StrictMode>
);
// 页面性能分析,可以删除
// reportWebVitals();
5.3、APP文件src->App.js
// 引入图片
import logo from './logo.svg';
// 引入组件样式
import './App.css';
function App() {
return (
<div className="App">
{/* 页面所有内容区域,可任意编辑 */}
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
6、编写组件测试
- 我们编写两个测试组件来进行测试,是否没问题
6.1、代码目录
6.2、组件代码
6.3、App.js
import "./App.css";
import Header from "./views/header/index.jsx";
import Content from "./views/content/index";
function App() {
return (
<div className="App">
<Header />
<Content />
</div>
);
}
export default App;
6.4、效果
7、样式冲突问题
- 当
header.js和content.js
有相同的类名,并且在该类名上有相同的样式,那么渲染就会出现问题- 会以后引入的组件样式优先,把上面组件相同类名的样式覆盖掉!
7.1、样式复现
7.1、组件代码
7.2、效果
7.2、问题解决
7.2.1、方法一
- 项目使用
less 或 sass
,在最外层包裹一个唯一的样式类名,其余子元素样式在这个样式里面写,这样编译出来的时候,每个样式外面都会有那个唯一的样式,就不会重复了- 但是由于项目是由多个人开发,这个唯一的名字还是有可能重复的
7.2.2、方法二
- 使用==
module
形式的css
,修改header.css
文件名,文件名中间添加.module
即可,修改为header.module.css==- 改为如上名称后,在
header
组件中,即可使用import headerCss from './header.module.css'
,引入样式模块- 最后直接在使用到该类名的地方,使用
headerCss.xxx
即可
代码
import headerCss from './header.module.css'
import React, { Component } from 'react'
export default class header extends Component {
render() {
console.log("tcc", headerCss);
return (
<div className={ headerCss.header }>
<span className={ headerCss.title }>header</span>
</div>
)
}
}
效果
8、插件推荐
- 该插件集成了很多内置代码简写生成方式,比如编写
rcc(快速创建类组件)、fcc(快速创建函数式组件)
等
编写TODO组件
- 上面我们讲解了能加速我们开发的方式
脚手架、插件
等,下面我们编写一个案例来更加深入体会开发步骤
1、效果
2、分组件
- 由于是练习,所以组件会分的比较多,其实实际开发,只需要把能被多次复用的地方提取为组件即可
- 这里共分为四个组件
Todo、TodoHeader、TodoItem、TodoFooter
3、组件结构
4、组件代码
记住,先把
App.css
里面所有样式删除,否则影响给咱们编写的代码一个默认样式
4.1、Todo
代码
import React, { Component } from 'react'
import './todo.css'
import TodoHeader from '../TodoHeader'
import TodoItem from '../TodoItem'
import TodoFooter from '../TodoFooter'
export default class Todo extends Component {
state = {
todos: []
}
render() {
return (
<div className='todo'>
<TodoHeader addTodo={this.addTodo}/>
<TodoItem todos={this.state.todos} handleItemChecked={this.handleItemChecked} handleDelete={this.handleDelete} />
<TodoFooter todos={this.state.todos} handleCheckAll={this.handleCheckAll} />
</div>
)
}
handleCheckAll = (isChecked) => {
this.setState({
todos: this.state.todos.map(item => {
return {
...item,
isChecked
}
})
})
}
/**
* 添加todo
* @param {Object} todo标题
*/
addTodo = (todo) => {
// 校验标题是否重复
if (this.state.todos.some(item => item.title === todo)) {
alert('任务名称重复!')
return
}
this.setState({
todos: [...this.state.todos, {title: todo, id: Date.now(), isChecked: false}]
})
}
/**
* 处理item中勾选框事件
* @param {Boolean} checked
* @param {Number} id
*/
handleItemChecked = (checked, id) => {
this.setState({
todos: this.state.todos.map(item => item.id === id ? {...item, isChecked: checked} : item)
})
}
/**
* 删除Todo
* @param {Number} id
*/
handleDelete = (id) => {
this.setState({
todos: this.state.todos.filter(item => item.id !== id)
})
}
}
样式
.todo {
padding: 10px;
border: 1px solid gray;
margin: 10vh auto;
width: 40vw;
}
4.2、TodoHeader
代码
import React, { Component } from 'react'
import './todoHeader.css'
export default class TodoHeader extends Component {
render() {
return (
<div className='todo-header'>
<input placeholder='请输入任务名称,按回车键确认' onKeyUp={this.handleInputKeyUp}></input>
</div>
)
}
handleInputKeyUp = (e) => {
if (!e.target.value || e.key !== 'Enter') {
return;
}
// 调用父组件的添加 todo 的方法,并且将输入框中的值传入方法中
this.props.addTodo(e.target.value);
// 清空输入框
e.target.value = '';
}
}
样式
.todo-header {
width: 100%;
height: 40px;
}
.todo-header input {
width: 100%;
height: 100%;
box-sizing: border-box;
}
4.3、TodoItem
代码
import React, { Component } from 'react'
import './todoItem.css'
export default class TodoItem extends Component {
render() {
const { todos, handleItemChecked, handleDelete } = this.props
return (
<div className='todo-item'>
{
todos.map(item => (
<div className='todo-item-list' key={item.id} onMouseOver={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
<input type="checkbox" checked={item.isChecked} onChange={e => handleItemChecked(e.target.checked, item.id)} />
<span>{item.title}</span>
<button className='deleteBtn' onClick={() => handleDelete(item.id)}>删除</button>
</div>
))
}
</div>
)
}
handleMouseOver = (e) => {
e.target.classList.add("showDeleteBtn")
}
handleMouseOut = (e) => {
e.target.classList.remove("showDeleteBtn")
}
}
样式
.todo-item {
max-height: 300px;
overflow-y: auto;
margin-top: 15px;
}
.todo-item .todo-item-list {
padding: 10px;
border: 1px solid #d9d9d9;
}
.todo-item input{
margin-left: unset;
cursor: pointer;
}
.todo-item .deleteBtn {
opacity: 0;
vertical-align: middle;
float: right;
background-color: #ff5959;
border: unset;
border-radius: 4px;
padding: 3px 10px;
transition: all 0.3s ease-in-out;
cursor: pointer;
}
.todo-item .showDeleteBtn .deleteBtn {
opacity: 1;
}
4.4、TodoFooter
代码
import React, { Component } from 'react'
import './todoFooter.css'
export default class TodoFooter extends Component {
state = {
}
render() {
const checkedCount = this.props.todos.filter(item => item.isChecked).length
const totalCount = this.props.todos.length
return (
<div className='todo-footer'>
<input type='checkbox' checked={!this.props.todos.some(item => !item.isChecked)} onChange={e => this.props.handleCheckAll(e.target.checked)} />
<span className='count'>已完成 {checkedCount} / 全部 {totalCount}</span>
</div>
)
}
}
样式
.todo-footer {
padding: 20px 0 10px;
}
.todo-footer .count {
margin-left: 10px;
}
代理/发送请求(Axios)
- 原生自带发送
ajax
请求有两种方式:
fetch
APInew XMLHttpRequest()
Axios
就是基于XMLHttpRequest
二次封装的工具类,可以很好的配置请求的发送以及请求和响应的拦截,并且返回的类型也是Promise
对象
1、Axios
1.1、安装
npm i axios
1.2、使用
import React, { Component } from "react";
// 引入 axios
import axios from "axios";
export default class App extends Component {
render() {
return (
<div>
<button onClick={this.sendAxios}>发送请求</button>
</div>
);
}
sendAxios = () => {
// 使用 axios.get 发送GET请求,返回值为Promise对象,可以使用.then()和.catch()处理
axios
.get("http://localhost:8888/getUser")
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
};
}
1.3、效果
1.4、跨域
- 上面报错其实是正常的,咱们接口调用写的没有问题
- 只不过前端会进行
跨域
拦截,当检测到你前台的端口为3000
,而后台的端口不为3000
时,后台是可以正常被调用的,只不过调用结果就会被前台拦截掉- 我这里前台端口为
3000
,后台端口为8888
,以你自己前台端口为准
2、配置代理
- 解决跨域在前台或者后台都可以解决,这里主要讲解前台解决
- 前台解决主要使用
代理
来解决,React
有两种方式配置代理
- 在
package.json
中配置
- 好处
- 配置简单
- 坏处
- 只能配置单一后端地址,如果要请求两个端口的接口则无法配置
- 新建
setupProxy.js
配置
- 好处
- 能进行更复杂的配置,多功能化
- 坏处
- 配置复杂一些
2.1、package.json
配置
- 通过在这个文件中添加
"proxy": "http://localhost:8888"
,这样所有接口调用http://localhost:3000/xxx
时,代理都会转为http://localhost:8888/xxx
去访问接口,并且响应结果不会被拦截
package.json文件
接口调用文件
结果
2.2、setupProxy.js
配置
- 使用这个方式,要先删除上面在
package.json
中的配置- 在
src
目录下新建setupProxy.js
文件,并写入如下配置
setupProxy.js文件
const { createProxyMiddleware } = require("http-proxy-middleware");
module.exports = function (app) {
/*
如果请求是/api/xxx ,则代理到http://localhost:8888/xxx
如果请求是http://localhost:3000/api/xxx,则代理到http://localhost:8888/xxx
*/
app.use(
createProxyMiddleware("/api", {
target: "http://localhost:8888",
// 控制服务器收到的请求头中的Host的值
changeOrigin: true,
// 重写请求路径
pathRewrite: {
"^/api": "",
},
})
);
};
App.js文件
import React, { Component } from "react";
// 引入 axios
import axios from "axios";
export default class App extends Component {
render() {
return (
<div>
<button onClick={this.sendAxios}>发送请求</button>
</div>
);
}
sendAxios = () => {
// 使用 axios.get 发送GET请求,返回值为Promise对象,可以使用.then()和.catch()处理
// http://localhost:3000/api/getUser 也可以
axios
.get("/api/getUser")
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
};
}
兄弟组件交互
- 这里会先写一个demo,然后再讲兄弟组件交互优化
1、Demo
- 这次做一个输入框查询数据的案例,很简单,涉及到兄弟组件之间的交互
- 后台代码很简单,自行编写即可
1.1、效果
1.2、App.js
import React, { Component } from "react";
import Search from "./components/search";
import List from "./components/list";
export default class App extends Component {
state = {
loading: false,
list: [],
isNoneData: false,
};
render() {
return (
<div>
<Search
changeLoading={this.changeLoading}
changeList={this.changeList}
/>
<List
list={this.state.list}
loading={this.state.loading}
isNoneData={this.state.isNoneData}
/>
</div>
);
}
// 修改 loading
changeLoading = (loading) => {
this.setState({ loading });
};
// 修改list数据
changeList = (list) => {
this.setState({ list });
// 判断是否有数据
this.setState({ isNoneData: !list || !list.length });
};
}
1.3、search组件
import React, { Component } from 'react'
// 引入 axios
import axios from "axios";
import './search.css'
export default class search extends Component {
render() {
return (
<div className='search'>
<input type="text" placeholder="请输入后按回车查询" onKeyUp={this.handleKeyUp} />
</div>
)
}
handleKeyUp = (e) => {
if (e.key !== 'Enter') {
return;
}
// 通知副组件。loading开启
this.props.changeLoading(true);
// 使用 ?userName=xxx 的方式传递参数
axios
.get(`/api/getUser?userName=${e.target.value}`)
.then((res) => {
this.props.changeList(res.data);
})
.catch((err) => {
console.log(err);
})
.finally(() => {
// 通知副组件。loading关闭
this.props.changeLoading(false);
});
}
}
1.4、list组件
import React, { Component } from 'react'
export default class List extends Component {
render() {
const { list, loading, isNoneData } = this.props
return (
<div>
{
loading ? <div>加载中...</div> :
isNoneData ? <div>暂无数据</div> :
<ul>
{
list.map((item) => {
return (
<li key={item.id}>姓名:{item.name},年龄:{item.age}</li>
)
})
}
</ul>
}
</div>
)
}
}
2、分析优化
- 通过上面观察,在
Search
中获取到数据以后,必须要==调用App.js
中的changeList
==方法,List
组件才能获取数据,并且loading
也是同样的做法,都要放到App.js
中,再通过props
进行父子传递- 有没有一种东西,让父子之间可以直接进行数据传递,而不用中间组件
App.js
- 消息订阅与发布机制:类似对讲机一样,定义一个频道,连接到这个频道的人可以随时发送消息,并且其他人可以立即收到消息,这种机制不仅适用于兄弟组件中沟通,适用于所有组件的沟通
- 下面我们使用主流的[pubsub-js](GitHub - mroderick/PubSubJS: Dependency free publish/subscribe for JavaScript)库
List
组件订阅消息Search
组件发布消息
2.1、安装
npm i pubsub-js
2.2、订阅消息(List组件)
import React, { Component } from 'react'
import PubSub from 'pubsub-js';
export default class List extends Component {
state = {
loading: false,
list: [],
isNoneData: false,
pubsub: null
};
// 组件挂载完成 订阅消息
componentDidMount() {
// 把返回值放到state中,方便取消订阅
this.state.pubsub = PubSub.subscribe('MY TOPIC', (msg, data) => {
// 这里利用 setState 方法只会合并,并不会覆盖机制
this.setState({
...data
})
});
}
// 取消订阅
componentWillUnmount() {
// 取消订阅
PubSub.unsubscribe(this.state.pubsub);
}
render() {
const { list, loading, isNoneData } = this.state
return (
<div>
{
loading ? <div>加载中...</div> :
isNoneData ? <div>暂无数据</div> :
<ul>
{
list.map((item) => {
return (
<li key={item.id}>姓名:{item.name},年龄:{item.age}</li>
)
})
}
</ul>
}
</div>
)
}
}
2.3、发布消息(Search组件)
import React, { Component } from 'react'
// 引入 axios
import axios from "axios";
import './search.css'
import PubSub from 'pubsub-js';
// 这里定义为常量,防止在代码中随意修改,或写错
const pubSubName = "MY TOPIC"
export default class search extends Component {
render() {
return (
<div className='search'>
<input type="text" placeholder="请输入后按回车查询" onKeyUp={this.handleKeyUp} />
</div>
)
}
handleKeyUp = (e) => {
if (e.key !== 'Enter') {
return;
}
// 发布消息。通知订阅相同消息的List组件loading开启
PubSub.publish(pubSubName, {loading: true});
// 使用 ?userName=xxx 的方式传递参数
axios
.get(`/api/getUser?userName=${e.target.value}`)
.then((res) => {
// 将响应结果传递给List组件
PubSub.publish(pubSubName, {list: res.data, isNoneData: !res.data || !res.data.length});
})
.catch((err) => {
console.log(err);
})
.finally(() => {
// 通知List组件。loading关闭
PubSub.publish(pubSubName, {loading: false});
});
}
}
2.4、App.jsx
import React, { Component } from "react";
import Search from "./components/search";
import List from "./components/list";
export default class App extends Component {
render() {
return (
<div className="app">
<Search />
<List />
</div>
);
}
}