注:!!!
由于内容较多,分2部分,后半部分:React基础篇(二)
一、React入门
1.React简介
-
Facebook开源的一个js库
-
一个用来动态构建用户界面的js库(只关注于视图(View))
2.React的特点
-
声明式编码
-
组件化编码
-
一次学习,随处编写(React Native 编写原生应用、服务器端渲染)
-
高效(优秀的Diffing算法, 编码人员不直接操作DOM)
-
单向数据流
3.React高效的原因
-
使用虚拟(virtual)DOM, 不总是直接操作页面真实DOM。(减少更新的次数)
-
高效的DOM Diffing算法, 最小化页面重绘。(减小页面更新的区域)
4.React的初体验
1). 导入相关js库文件(react.development.js, react-dom.development.js, babel.min.js)
-
引入react核心库
-
引入react-dom, 用于支持react操作DOM
-
引入babel, 用于将jsx转为js
2). 创建虚拟DOM,然后渲染虚拟DOM到页面
1). 导入相关js库文件(react.development.js, react-dom.development.js, babel.min.js)
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom, 用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel, 用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
2). 创建虚拟DOM,然后渲染虚拟DOM到页面:
<!-- 准备好一个“容器” -->
<div id="test"></div>
<script type="text/babel"> /* 此处一定要写babel */
//1.创建虚拟DOM
const VDOM = <h1>Hello,React</h1> /* 此处一定不要写引号,因为不是字符串 */
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM, document.getElementById('test')); //该句话不是追加,是替换
</script>
- 注: 此时只是测试语法使用, 并不是真实项目开发使用!
5.虚拟DOM的两种创建方式
1.纯JS方式(一般不用)
React.createElement('h1',{id:'title'}, React.createElement('span',{},'Hello,React'))
2.JSX方式
const VDOM = ( /* 此处一定不要写引号,因为不是字符串 */
<h1 id="title">
<span>Hello,React</span>
</h1>
)
实例:
1.纯JS方式(一般不用)
<!-- 准备好一个“容器” -->
<div id="test"></div>
<script type="text/javascript" >
//1.创建虚拟DOM
const VDOM = React.createElement('h1',{id:'title'}, React.createElement('span',{},'Hello,React'))
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM,document.getElementById('test'))
</script>
2.JSX方式
<!-- 准备好一个“容器” -->
<div id="test"></div>
<script type="text/babel" > /* 此处一定要写babel */
//1.创建虚拟DOM
const VDOM = ( /* 此处一定不要写引号,因为不是字符串 */
<h1 id="title">
<span>Hello,React</span>
</h1>
)
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM,document.getElementById('test'))
</script>
6.虚拟DOM与真实DOM
关于虚拟DOM:
1.本质是Object类型的对象(一般对象),React源码里称 ReactElement 对象。
2.虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性。
3.虚拟DOM最终会被React转化为真实DOM,呈现在页面上。
实例:
<div id="test"></div>
<div id="demo"></div>
<script type="text/babel" > /* 此处一定要写babel */
//1.创建虚拟DOM
const VDOM = ( /* 此处一定不要写引号,因为不是字符串 */
<h1 id="title">
<span>Hello,React</span>
</h1>
)
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM,document.getElementById('test'));
//真实DOM
const TDOM = document.getElementById('demo');
console.log('虚拟DOM',VDOM);
console.log('真实DOM',TDOM);
// debugger;
console.log(typeof VDOM); //object
console.log(VDOM instanceof Object); //true
</script>
7.JSX的理解和使用
1) 理解
1.全称: JavaScript XML
2.react定义的一种类似于XML的JS扩展语法: JS + XML
3.JSX本质 是React. createElement (component, props, ...children)方法的 语法糖
4.作用: 用来简化创建虚拟DOM
1) 写法:
var ele = <h1>Hello JSX!</h1>
2) 注1: 它不是字符串, 也不是HTML/XML标签
3) 注2: 它最终产生的就是一个JS对象(ReactElement对象)
2) jsx语法规则:
1.定义虚拟DOM时,不要写引号。
2.JSX中嵌入 变量 时要用{}。
- 当变量是Number、String、Array类型时,可以直接显示,数组会自动展开所有成员。
- 当变量是null、undefined、Boolean类型时,内容为空。
- 如果希望可以显示null、undefined、Boolean,那么需要转成字符串。
- 转换的方式有很多,比如toString方法、和空字符串拼接,String(变量)等方式。
- 对象类型不能作为React子元素放在标签体中直接显示。
<div id="app"></div>
<script type="text/babel">
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
// 1.在{}中可以正常显示显示的内容
name: "why", // String
age: 18, // Number
names: ["abc", "cba", "nba"], // Array
// 2.在{}中不能显示(忽略)
test1: null, // null
test2: undefined, // undefined
test3: true, // Boolean
flag: true,
// 3.对象不能作为jsx的子类
friend: {
name: "kobe",
age: 40
}
}
}
render() {
return (
<div>
<h2>{this.state.name}</h2>
<h2>{this.state.age}</h2>
<h2>{this.state.names}</h2>
<h2>{this.state.test1 + ""}</h2>
<h2>{this.state.test2 + ""}</h2>
<h2>{this.state.test3.toString()}</h2>
<h2>{this.state.flag ? "你好啊": null}</h2>
<h2>{this.state.friend}</h2>
</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById("app"));
</script>
3.JSX中嵌入 表达式 时要用{}。
-
运算表达式
-
三元运算符
-
执行一个函数
<div id="app"></div>
<script type="text/babel">
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
firstname: "kobe",
lastname: "bryant",
isLogin: false
}
}
render() {
const { firstname, lastname, isLogin } = this.state;
return (
<div>
{/*1.运算符表达式*/}
<h2>{ firstname + " " + lastname }</h2>
<h2>{20 * 50}</h2>
{/*2.三元表达式*/}
<h2>{ isLogin ? "欢迎回来~": "请先登录~" }</h2>
{/*3.进行函数调用*/}
<h2>{this.getFullName()}</h2>
</div>
)
}
getFullName() {
return this.state.firstname + " " + this.state.lastname;
}
}
ReactDOM.render(<App/>, document.getElementById("app"));
</script>
4.jsx绑定元素属性
-
比如元素都会有title属性
-
比如img元素会有src属性
-
比如a元素会有href属性
-
比如元素可能需要绑定class
-
比如原生使用内联样式style
-
注:
-
样式的类名指定不要用class,要用className。
-
内联样式,要用 style={{key: value}} 的形式去写。两个单词的css样式要写 小驼峰 的形式。 将样式以key: value的形式,放在对象里, 最后放入{}中。React 会在指定元素数字后自动添加 px。
-
<div id="app"></div>
<script type="text/babel">
function getSizeImage(imgUrl, size) {
return imgUrl + `?param=${size}x${size}`
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
title: "标题",
imgUrl: "http://p2.music.126.net/L8IDEWMk_6vyT0asSkPgXw==/109951163990535633.jpg",
link: "http://www.baidu.com",
active: true
}
}
render() {
const { title, imgUrl, link, active } = this.state;
return (
<div>
{/* 1.绑定普通属性 */}
<h2 title={title}>我是标题</h2>
<img src={getSizeImage(imgUrl, 140)} alt=""/>
<a href={link} target="_blank">百度一下</a>
{/* 2.绑定class */}
<div className="box title">我是div元素</div>
{/* 动态绑定class */}
<div className={"box title " + (active ? "active": "")}>我也是div元素</div>
<label htmlFor=""></label>
{/* 3.绑定style */}
<div style={{color: "red", fontSize: "50px"}}>我是div,绑定style属性</div>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById("app"));
</script>
5.JSX的顶层只能有一个根元素(模板),所以很多时候会在外层包裹一个div原生(或者使用Fragment)。
6.标签必须闭合,如果是单标签,必须以/>结尾。
7.jsx标签中的注释:
{/*注释...*/}
8.jsx标签外的注释:
/*注释...*/
9.标签首字母
(1).若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。
(2).若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
10.每个列表的li都应该有唯一的key, key应该设置在兄弟节点,且只是在兄弟节点之间必须唯一。
注:
- 一定注意区分:【js语句(代码)】与【js表达式】
1.表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
下面这些都是表达式:
(1). a
(2). a+b
(3). demo(1)
(4). arr.map() ===> 用于加工数组
(5). function test() {}
2.语句(代码)
下面这些都是语句:
(1). if(){}
(2). for(){}
(3). switch(){case:xxx}
实例:
<!-- 准备好一个“容器” -->
<div id="test"></div>
<script type="text/babel">
//准备一些数据
const myId = 'aTgUiGu'
const myData = 'HeLlo,rEaCt'
//1.创建虚拟DOM
const VDOM = (
<div>
<h2 className="title" id={myId.toLowerCase()}>
<span style={{color:'white',fontSize:'29px'}}>{myData.toLowerCase()}</span>
</h2>
<h2 className="title" id={myId.toUpperCase()}>
<span style={{color:'white',fontSize:'29px'}}>{myData.toLowerCase()}</span>
</h2>
<input type="text"/>
<peiqi>123</peiqi>
</div>
)
//2.渲染虚拟DOM到页面, 即虚拟DOM => 真实DOM,呈现在页面
ReactDOM.render(VDOM,document.getElementById('test'))
</script>
jsx练习-动态展示列表
<div id="test"></div>
<script type="text/babel">
//模拟后台返回的数据
const arr = ['Angular','React','Vue']
//1.创建虚拟DOM
const VDOM = (
<div>
<h1>前端框架列表</h1>
<ul>
{
//map方法专门用于加工数组,React中大量使用map方法
arr.map((item,index)=>{
return <li key={index}>{item}</li>
})
//不能使用forEach,因为返回值永远是undefined
/* arr.forEach((item,index)=>{
return <li key={index}>{item}</li> //这么写是错误的
}) */
}
</ul>
</div>
);
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM,document.getElementById('test'))
</script>
8.模块与组件、模块化与组件化的理解
1). 模块与组件
1.模块:
-
理解: 向外提供特定功能的js程序, 一般就是一个js文件
-
为什么要拆成模块: 随着业务逻辑增加,js代码更多更复杂
-
作用: 复用js, 简化js的编写, 提高js运行效率
2.组件:
-
理解: 用来实现特定(局部)界面功能效果的代码集合(html/css/js/img)
-
为什么要用组件: 一个界面的功能太复杂了
-
作用: 复用编码, 简化项目界面编码, 提高运行效率
2). 模块化与组件化
1.模块化:
- 当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
2.组件化:
- 当应用是以多组件的方式实现, 这个应用就是一个组件化的应用
9.命令式编程与声明式编程
1.声明式编程
- 只关注做什么,而不关注怎么做(流程),类似于填空题。每做一个操作,都是给计算机(浏览器)一步步命令。
2.命令式编程
- 要关注做什么和怎么做(流程),类似于问答题
3.虚拟DOM帮助我们从命令式编程转到了声明式编程的模式
4.声明式编程的方式赋予了React声明式的API:
-
你只需要告诉React希望让UI是什么状态;
-
React来确保DOM和这些状态是匹配的;
-
你不需要直接进行DOM操作,只可以从手动更改DOM、属性操作、事件处理中解放出来;
需求:
原数组:[1, 3, 5, 7],得到一个新的数组, 数组中每个元素都比原数组中对应的元素大10: [11, 13, 15, 17]
var arr = [1, 3, 5, 7];
// 1-命令式编程
var arr2 = [];
for(var i = 0;i<arr.length;i++) {
arr2.push(arr[i] + 10);
}
console.log(arr2);
// 2-声明式编程
var arr3 = arr.map(function(item){
return item + 10;
})
注:
-
声明式编程是建立命令式编程的基础上
-
数组中常见声明式方法:map() / filter() / reduce() / forEach() / find() / findIndex()
二、React组件化开发
1.组件的创建与使用
1). 组件分类: 函数式组件 / 类式组件
2). 创建组件(标签)的方式
方式1: 函数式组件(简单组件/无状态组件)
function MyComponent1(props) {
return <h1>自定义组件标题11111</h1>;
}
注:
- 没有生命周期,也会被更新并挂载,但是没有生命周期函数
- 没有this(组件实例)
- 没有内部状态(state)
方式2: 类式组件(复杂组件/有状态组件, 推荐使用!)
class MyComponent2 extends React.Component {
render () {
return <h1>自定义组件标题33333</h1>;
}
}
3). 渲染组件(标签)
ReactDOM.render(<MyComponent1 />, cotainerElement);
4). ReactDOM.render()渲染函数式组件(标签)的基本流程
1.React解析组件标签,寻找MyComponent组件:
(1).若找到了,则进行下一步
(2).若未找到,则报错:MyComponent is not defined
2.React发现MyComponent是用函数定义的,随后React调用该函数,将返回的虚拟DOM转为真实DOM,渲染到页面上。
5). ReactDOM.render()渲染类式组件(标签)的基本流程
1.React解析组件标签,寻找MyComponent组件
(1).若找到了,则进行下一步
(2).若未找到,则报错:MyComponent is not defined
2.React发现组件是使用类定义的,随后React帮我们去new了一个MyComponent的实例:mc, 并通过mc调用到了MyComponent原型上的render方法,即:mc.render()
备注:此处的mc只是我们分析问题时的一个代号,React底层用的是别的名字。
3.将render返回的虚拟DOM转为真实DOM,随后渲染到页面上。
6). render函数的返回值
- 当 render 被调用时,它会检查 this.props 和 this.state 的变化并返回以下类型之一:
- React 元素:
- 通常通过 JSX 创建。
- 例如,< div / > 会被 React 渲染为 DOM 节点,< MyComponent / > 会被 React 渲染为自定义组件。
- 无论是 < div / > 还是 < MyComponent / > 均为 React 元素。
- 数组 或 fragments:使得 render 方法可以返回多个元素。
- Portals:可以渲染子节点到不同的 DOM 子树中。
- 字符串 或 数值类型:它们在 DOM 中会被渲染为文本节点。
- 布尔类型 或 null:什么都不渲染。
注:
1.组件名必须首字母大写
2.所谓的简单组件,就是没有状态的组件
3.所谓的复杂组件,就是有状态的组件
实例:
1.创建函数式组件
<div id="test"></div>
<script type="text/babel">
//1.创建函数式组件
function MyComponent (){
console.log(this) //此处的this是 undefined, 因为jsx编译为js后babel开启了严格模式
return <h2>我是用函数定义的组件(适用于:【简单组件】的定义)</h2>
}
//2.渲染组件到页面
ReactDOM.render(<MyComponent/>,document.getElementById('test'));
</script>
2.创建类式组件
<div id="test"></div>
<script type="text/babel">
//1.创建类式组件
class MyComponent extends React.Component {
render(){
//render是放在哪里的?—— MyComponent的原型对象上,供实例使用。
//render中的this是谁?—— MyComponent类的实例对象 <=> MyComponent组件实例对象。
console.log('render中的this:', this);
return <h2>我是用类定义的组件(适用于:【复杂组件】的定义)</h2>;
}
}
//2.渲染组件到页面
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
</script>
2.组件实例的3大属性1: state
1.state是组件实例对象最重要的属性, 值是对象(可以包含多个key-value的组合)
2.组件被称为"状态机", 通过更新组件的state来更新对应的页面显示(重新渲染组件)
2.构造函数初始化指定:
constructor(props) {
super(props);
this.state = {
stateName1 : stateValue1,
stateName2 : stateValue2
};
}
4.class里直接初始化指定( 简写方式 - 推荐! ):
class Weather extends React.Component{
state = {
isHot:true,
wind:'微风'
}
...
}
5.读取显示:
this.state.stateName
6.更新状态 --> 更新界面:
this.setState({
stateProp1 : value1,
stateProp2 : value2
});
7.注意
1.组件中render方法中的this为组件实例对象
2.状态数据,不能直接修改或更新,必须调用setState()方法修改或更新
实例:
需求: 定义一个展示天气信息的组件
1.默认展示天气炎热 或 凉爽
2.点击文字切换天气
<div id="test"></div>
<script type="text/babel">
//1.创建组件
class Weather extends React.Component{
//构造器调用几次? ———— 1次
constructor(props){
console.log('constructor');
super(props);
//初始化状态
this.state = {isHot:false,wind:'微风'};
//(写法1)-用bind解决changeWeather中this指向问题
//用改变原型上changeWeather中this指向,返回的新函数添加给实例
//官方说法-为事件处理函数绑定实例
// this.changeWeather = this.changeWeather.bind(this);
}
//render调用几次? ———— 1 + n次,1是初始化的那次 n是状态更新的次数
render(){
console.log('render');
//读取状态
const {isHot,wind} = this.state;
//(写法2)-直接在事件绑定处用bind解决changeWeather中this指向问题,节省了在构造函数里再写代码给实例本身加changeWeather方法
return <h1 onClick = {this.changeWeather.bind(this)}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>;
}
//changeWeather调用几次? ———— 点几次调几次
changeWeather(){
//changeWeather放在哪里? ———— Weather的原型对象上,供实例使用
//由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
//类中的方法默认开启了局部的严格模式,以及babel编译后全局开启了严格模式,所以changeWeather中的this为undefined
console.log('changeWeather');
//获取原来的isHot值
const isHot = this.state.isHot;
//严重注意:状态必须通过setState进行更新,且更新是一种合并,不是替换。
this.setState({isHot:!isHot}); //setState干了两件事:1.帮你更改状态 2.重新调用render
console.log(this);
//严重注意:状态(state)不可直接更改,下面这行就是直接更改!!!
//this.state.isHot = !isHot //这是错误的写法
}
}
//2.渲染组件到页面
ReactDOM.render(<Weather/>,document.getElementById('test'));
</script>
1) state的简写方式
<div id="test"></div>
<script type="text/babel">
//1.创建组件
class Weather extends React.Component{
//初始化状态
state = {isHot:false,wind:'微风'};
render(){
const {isHot,wind} = this.state;
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>;
}
//自定义方法-类组件中事件的回调———要用赋值语句的形式+箭头函数
changeWeather = ()=>{
const isHot = this.state.isHot;
this.setState({isHot:!isHot});
this.haha();
}
//haha不作为事件的回调使用,那么haha就没有必要:赋值语句+箭头函数去写
haha(){
console.log('哈哈');
}
}
//2.渲染组件到页面
ReactDOM.render(<Weather/>,document.getElementById('test'));
</script>
2) 扩展实例-天气3状态切换
<div id="test"></div>
<script type="text/babel" >
//1.定义组件
class Weather extends React.Component{
//初始化状态
state ={
temp: 1 //1炎热 2温暖 3凉爽
}
render(){
const {temp} = this.state
return (
<div>
<h2>
今天天气很{
temp === 1 ? '炎热' :
temp === 2 ? '温暖' :
temp === 3 ? '凉爽' : '未知'
}
</h2>
<button onClick={this.changeWeather}>点我切换天气</button>
</div>
)
}
changeWeather = ()=>{
//获取原状态
let {temp} = this.state
//自增
temp += 1
//校验数据
if(temp>3) temp = 1
//更新状态
this.setState({temp})
}
}
//2.渲染组件
ReactDOM.render(<Weather/>,document.getElementById('test'))
</script>
3.组件实例的3大属性2: props
1.每个组件实例对象都会有props(properties的简写)属性
2.组件标签的所有属性都保存在props中
3.在组件内部读取属性: this.props.propertyName
4.props是只读的
5.作用:
1) 通过标签属性从组件外向组件内传递变化的数据(父传子)
2) 注意: 组件内部不要修改props数据
6.对props中的属性值进行类型限制和必要性限制
1) 使用prop-types库进限制(需要引入prop-types库)
Person.propTypes = {
name: PropTypes.string.isRequired, //限制name必传,且为字符串
age: PropTypes.number, //限制age为数值
speak:PropTypes.func, //限制speak为函数
}
- isRequired为必传属性
2) 简写方式
//对标签属性进行类型、必要性的限制
static propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string, //限制sex为字符串
age:PropTypes.number, //限制age为数值
}
7.属性展开 (组件传参简写)- 将对象的所有属性通过props传递,即批量传递props(标签属性)
//在react和babel作用下,对象可以用...拆对象,但只能用于组件传参!
<Person {...person}/>
8.默认属性值
Person.defaultProps = {
sex:'男', //sex默认值为男
age:18 //age默认值为18
}
简写方式:
static defaultProps = {
sex:'男', //sex默认值为男
age:18 //age默认值为18
}
9.类式组件的构造函数接收props
constructor(props){
super(props);
console.log(props); //打印所有属性
}
注: 可以不写构造函数,源码默认给实例对象加上了props,即可以直接使用this.props.xxx获取。
10.函数式组件使用props
- 函数式组件只能通过传参使用props属性
<div id="test"></div>
<script type="text/babel">
//创建组件
//函数式组件只能通过传参使用props属性
function Person (props){
const {name,age,sex} = props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
Person.propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string, //限制sex为字符串
age:PropTypes.number, //限制age为数值
}
//指定默认标签属性值
Person.defaultProps = {
sex:'男', //sex默认值为男
age:18 //age默认值为18
}
//渲染组件到页面
ReactDOM.render(<Person name="jerry"/>,document.getElementById('test'))
</script>
实例:
需求: 自定义用来显示一个人员信息的组件
1)姓名必须指定,且为字符串类型
2)性别为字符串类型,如果性别没有指定,默认为男
3)年龄为字符串类型,且为数字类型,默认值为18
//1.props基本使用
<div id="test1"></div>
<div id="test2"></div>
<div id="test3"></div>
<script type="text/babel">
//创建组件
class Person extends React.Component{
render(){
// console.log(this);
const {name,age,sex} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age+1}</li>
</ul>
)
}
}
//渲染组件到页面
ReactDOM.render(<Person name="jerry" age={19} sex="男"/>,document.getElementById('test1'))
ReactDOM.render(<Person name="tom" age={18} sex="女"/>,document.getElementById('test2'))
//模拟一个人的信息(真实项目开发中,一定是从服务器获取到的)
const p = {name:'老刘',age:18,sex:'女'}
// console.log('@',...p); //此处的...p不奏效
// ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))
//组件传参简写-即批量传递props(标签属性)
//在react库和babel作用下可以对象可以用...拆对象,但只能用于组件传参!
ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))
</script>
//2.对props进行限制
<div id="test1"></div>
<div id="test2"></div>
<div id="test3"></div>
<script type="text/babel">
//创建组件
class Person extends React.Component{
render(){
// console.log(this);
const {name,age,sex} = this.props
//props是只读的
//this.props.name = 'jack' //此行代码会报错,因为props是只读的
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age+1}</li>
</ul>
)
}
}
//对标签属性进行类型、必要性的限制
Person.propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string, //限制sex为字符串
age:PropTypes.number, //限制age为数值
//限制函数用func
speak:PropTypes.func, //限制speak为函数
}
//指定默认标签属性值
Person.defaultProps = {
sex:'男', //sex默认值为男
age:18 //age默认值为18
}
//渲染组件到页面
ReactDOM.render(<Person name="jack" speak={speak}/>,document.getElementById('test1'))
ReactDOM.render(<Person name="tom" age={18} sex="女"/>,document.getElementById('test2'))
const p = {name:'老刘',age:18,sex:'女'}
// console.log('@',...p);
// ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))
ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))
function speak(){
console.log('我说话了');
}
</script>
//3.props的简写方式
<div id="test1"></div>
<script type="text/babel">
//创建组件
class Person extends React.Component{
//构造器一般能省则省
constructor(props){
//注意:构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props
// console.log(props);
super(props);
//这种写法很少
console.log('constructor',this.props);
}
//对标签属性进行类型、必要性的限制
static propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string, //限制sex为字符串
age:PropTypes.number, //限制age为数值
}
//指定默认标签属性值
static defaultProps = {
sex:'男', //sex默认值为男
age:18 //age默认值为18
}
render(){
// console.log(this);
const {name,age,sex} = this.props
//props是只读的
//this.props.name = 'jack' //此行代码会报错,因为props是只读的
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age+1}</li>
</ul>
)
}
}
//渲染组件到页面
ReactDOM.render(<Person name="jerry"/>,document.getElementById('test1'))
</script>
11.子组件传父组件数据 - 通过传递函数属性的props实现
- 某些情况,我们也需要子组件向父组件传递消息:
- 在vue中是通过自定义事件来完成的;
- 在React中同样是通过props传递消息,只是让父组件给子组件传递一个回调函数,在子组件中调用这个函数即可;
实例:
import React, { Component } from 'react';
class CounterButton extends Component {
render() {
const {onClick} = this.props;
return <button onClick={onClick}>+1</button>
}
}
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
render() {
return (
<div>
<h2>当前计数: {this.state.counter}</h2>
<button onClick={e => this.increment()}>+</button>
{/* 父传给子函数,子调用,即可实现子传父 */}
<CounterButton onClick={e => this.increment()} name="why"/>
</div>
)
}
increment() {
this.setState({
counter: this.state.counter + 1
})
}
}
父子通信案例:tab切换
//TabControl.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class TabControl extends Component {
constructor(props) {
super(props);
this.state = {
currentIndex: 0
}
}
render() {
const { titles } = this.props;
const {currentIndex} = this.state;
return (
<div className="tab-control">
{
titles.map((item, index) => {
return (
<div key={item}
className={"tab-item " + (index === currentIndex ? "active": "")}
onClick={e => this.itemClick(index)}>
<span>{item}</span>
</div>
)
})
}
</div>
)
}
itemClick(index) {
this.setState({
currentIndex: index
})
const {itemClick} = this.props;
itemClick(index);
}
}
TabControl.propTypes = {
titles: PropTypes.array.isRequired
}
//app.js
import React, { Component } from 'react';
import TabControl from './TabControl';
export default class App extends Component {
constructor(props) {
super(props);
this.titles = ['新款', '精选', '流行'];
this.state = {
currentTitle: "新款",
currentIndex: 0
}
}
render() {
const {currentTitle} = this.state;
return (
<div>
<TabControl itemClick={index => this.itemClick(index)} titles={this.titles} />
<h2>{currentTitle}</h2>
</div>
)
}
itemClick(index) {
this.setState({
currentTitle: this.titles[index]
})
}
}
注意:css类的动态绑定-字符串拼接!
12.children props 与 jsx props - 传入结构(标签)
1)children props:
- 传入 - 多个则以数组保存
<NavBar name="" title="" className="">
<span>aaa</span>
<strong>bbb</strong>
<a href="/#">ccc</a>
</NavBar>
- 读取 - 多个以数组下标读取
<div className="nav-left">
{this.props.children[0]}
</div>
<div className="nav-item nav-center">
{this.props.children[1]}
</div>
<div className="nav-item nav-right">
{this.props.children[2]}
</div>
2)jsx props (推荐!)
- 传入 - 多个保存到对象
<NavBar2 leftSlot={<span>aaa</span>}
centerSlot={<strong>bbb</strong>}
rightSlot={<a href="/#">ccc</a>}/>
- 读取 - 多个以key获取
<div className="nav-left">
{this.props.leftSlot}
</div>
<div className="nav-item nav-center">
{this.props.centerSlot}
</div>
<div className="nav-item nav-right">
{this.props.rightSlot}
</div>
注:
- react 实现 vue 的插槽
- 缺点:不能携带数据
案例:app顶部导航条
//app.js
import React, { Component } from 'react';
import NavBar from './NavBar';
import NavBar2 from './NavBar2';
export default class App extends Component {
render() {
const leftJsx = <span>aaa</span>;
return (
<div>
<NavBar name="" title="" className="">
<span>aaa</span>
<strong>bbb</strong>
<a href="/#">ccc</a>
</NavBar>
<NavBar2 leftSlot={leftJsx}
centerSlot={<strong>bbb</strong>}
rightSlot={<a href="/#">ccc</a>}/>
</div>
)
}
}
//NavBar.js
import React, { Component } from 'react'
export default class NavBar extends Component {
render() {
// this.props.children;
return (
<div className="nav-item nav-bar">
<div className="nav-left">
{this.props.children[0]}
</div>
<div className="nav-item nav-center">
{this.props.children[1]}
</div>
<div className="nav-item nav-right">
{this.props.children[2]}
</div>
</div>
)
}
}
//NavBar2.js
import React, { Component } from 'react'
export default class NavBar2 extends Component {
render() {
const {leftSlot, centerSlot, rightSlot} = this.props;
return (
<div className="nav-item nav-bar">
<div className="nav-left">
{leftSlot}
</div>
<div className="nav-item nav-center">
{centerSlot}
</div>
<div className="nav-item nav-right">
{rightSlot}
</div>
</div>
)
}
}
13.render props
- 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性
<A render={(data) => <C data={data}></C>}></A>
A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data}
实例
import React, { Component } from 'react'
import './index.css'
import C from '../1_setState'
export default class Parent extends Component {
render() {
return (
<div className="parent">
<h3>我是Parent组件</h3>
<A render={(name)=><C name={name}/>}/>
</div>
)
}
}
class A extends Component {
state = {name:'tom'}
render() {
console.log(this.props);
const {name} = this.state
return (
<div className="a">
<h3>我是A组件</h3>
{this.props.render(name)}
</div>
)
}
}
class C extends Component {
render() {
console.log('C--render');
return (
<div className="c">
<h3>我是C组件,{this.props.name}</h3>
</div>
)
}
}
4.组件实例的3大属性3: refs
1.组件内包含ref属性的标签元素的集合对象
2.组件内的标签可以定义ref属性来标识自己
3.作用: 找到组件内部的真实dom元素对象, 进而操作它
4.ref分类
1) 字符串形式的ref (即将被弃用!)
<input ref="input1"/>
- 在组件内部获得标签对象: this.refs.refName(只是得到了标签元素对象)
2) 回调形式的ref
<input ref={(c) => {this.input1 = c}}/>
- 在组件内部获得标签节点: this.input1
3) createRef创建ref容器 (推荐!)
myRef = React.createRef()
<input ref={this.myRef}/>
- 在组件内部获得标签节点: this.myRef.current
实例:
需求: 自定义组件, 功能说明如下:
1.点击按钮, 提示第一个输入框中的值
2.当第2个输入框失去焦点时, 提示这个输入框中的值
//1.字符串形式的ref(即将被弃用!)
<div id="test"></div>
<script type="text/babel">
//字符串形式的ref有效率问题,即将被弃用!
//创建组件
class Demo extends React.Component{
//展示左侧输入框的数据
showData = ()=>{
//refs通过ref收集的是真实的DOM!
const {input1} = this.refs;
alert(input1.value);
}
//展示右侧输入框的数据
showData2 = ()=>{
const {input2} = this.refs;
alert(input2.value);
}
render(){
return(
<div>
<input ref="input1" type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'))
</script>
//2.回调形式的ref
<div id="test"></div>
<script type="text/babel">
//创建组件
class Demo extends React.Component{
//展示左侧输入框的数据
showData = ()=>{
const {input1} = this;
alert(input1.value);
}
//展示右侧输入框的数据
showData2 = ()=>{
const {input2} = this;
alert(input2.value);
}
render(){
return(
<div>
<input ref={c => this.input1 = c} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} ref={c => this.input2 = c} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'))
</script>
//3.回调ref中回调执行次数的问题
<div id="test"></div>
<script type="text/babel">
//关于回调 refs 的说明
//如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,
//第一次传入参数 null,然后第二次会传入参数 DOM 元素。
//这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。
//通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
//创建组件
class Demo extends React.Component{
state = {isHot:false};
showInfo = ()=>{
const {input1} = this;
alert(input1.value);
}
changeWeather = ()=>{
//获取原来的状态
const {isHot} = this.state;
//更新状态
this.setState({isHot:!isHot});
}
saveInput = (c)=>{
this.input1 = c;
console.log('@',c);
}
render(){
const {isHot} = this.state;
return(
<div>
<h2>今天天气很{isHot ? '炎热':'凉爽'}</h2>
{/*<input ref={(c)=>{this.input1 = c;console.log('@',c);}} type="text"/><br/><br/>*/}
<input ref={this.saveInput} type="text"/><br/><br/>
<button onClick={this.showInfo}>点我提示输入的数据</button>
<button onClick={this.changeWeather}>点我切换天气</button>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test'));
</script>
//4.createRef创建ref容器
<div id="test"></div>
<script type="text/babel">
//创建组件
class Demo extends React.Component{
/*
React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”的
*/
myRef = React.createRef();
myRef2 = React.createRef();
//展示左侧输入框的数据
showData = ()=>{
alert(this.myRef.current.value);
}
//展示右侧输入框的数据
showData2 = ()=>{
alert(this.myRef2.current.value);
}
render(){
return(
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'));
</script>
5.ref用于标识组件 - 子传父数据
- 当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性
- 父子组件间通信: 父组件需要子组件数据(给子组件设置ref,获取子组件数据)
- 父组件得到子组件对象, 从而可以读取其状态数据或调用其方法更新其状态数据
- 你不能在函数组件上使用 ref 属性,因为他们没有实例
- 但是某些时候,我们可能想要获取函数式组件中的某个DOM元素
- 这个时候我们可以通过 React.forwardRef ,后面也会学习 hooks 中如何使用ref
实例:
import React, { PureComponent, createRef, Component } from 'react';
class Counter extends PureComponent {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
render() {
return (
<div>
<h2>当前计数: {this.state.counter}</h2>
<button onClick={e => this.increment()}>+1</button>
</div>
)
}
increment() {
this.setState({
counter: this.state.counter + 1
})
}
}
export default class App extends PureComponent {
constructor(props) {
super(props);
this.titleRef = createRef();
this.counterRef = createRef();
this.titleEl = null;
}
render() {
return (
<div>
{/* <h2 ref=字符串/对象/函数>Hello React</h2> */}
<h2 ref="titleRef">Hello React</h2>
{/* 目前React推荐的方式 */}
<h2 ref={this.titleRef}>Hello React</h2>
<h2 ref={arg => this.titleEl = arg}>Hello React</h2>
<button onClick={e => this.changeText()}>改变文本</button>
<hr/>
<Counter ref={this.counterRef}/>
<button onClick={e => this.appBtnClick()}>App按钮</button>
</div>
)
}
changeText() {
// 1.使用方式一: 字符串(不推荐, 后续的更新会删除)
this.refs.titleRef.innerHTML = "Hello Admin";
// 2.使用方式二: 对象方式
this.titleRef.current.innerHTML = "Hello JavaScript";
// 3.使用方式三: 回调函数方式
this.titleEl.innerHTML = "Hello TypeScript";
}
appBtnClick() {
this.counterRef.current.increment();
}
}
class App extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Counter title={}></Counter>
</div>
)
}
}
5.事件处理+事件处理函数中this丢失问题
1.通过onXxx属性指定事件处理函数(注意大小写)
1) React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 —————— 为了更好的兼容性
2) React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) ———————— 为了更高效
2.通过event.target得到发生事件的DOM元素对象 —————————— 不要过度使用ref
3.事件处理函数中this为undefined,如何解决?
a)使用bind()给事件处理函数显示绑定this
b)在组件中自定义事件处理函数时,使用箭头函数
c)(推荐!) 指定事件处理函数时使用箭头函数包括自己定义的函数,并在其内部调用自己定义的函数。
示例:
<div id="app"></div>
<script type="text/babel">
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
message: "你好啊",
counter: 100
}
// 1.方案一(写法2): bind绑定this(显示绑定)
// this.btnClick = this.btnClick.bind(this);
}
render() {
return (
<div>
{/* 1.方案一(写法1): bind绑定this(显示绑定) */}
<button onClick={this.btnClick.bind(this)}>按钮1</button>
<button onClick={this.btnClick.bind(this)}>按钮2</button>
<button onClick={this.btnClick.bind(this)}>按钮3</button>
{/* 2.方案二: 定义函数时, 使用箭头函数 */}
<button onClick={this.increment}>+1</button>
{/* 2.方案三(推荐): 直接传入一个箭头函数, 在箭头函数中调用需要执行的函数*/}
<button onClick={() => { this.decrement("why") }}>-1</button>
</div>
)
}
btnClick() {
console.log(this.state.message);
}
// increment() {
// console.log(this.state.counter);
// }
// 箭头函数中永远不绑定this
increment = () => {
console.log(this.state.counter);
}
decrement(name) {
console.log(this.state.counter, name);
}
}
ReactDOM.render(<App/>, document.getElementById("app"));
</script>
4.事件处理函数传参
示例:
<div id="app"></div>
<script type="text/babel">
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: ["大话西游", "海王", "流浪地球", "盗梦空间"]
}
this.btnClick = this.btnClick.bind(this);
}
render() {
return (
<div>
<button onClick={this.btnClick}>按钮</button>
<ul>
{
this.state.movies.map((item, index, arr) => {
return (
<li className="item"
//使用此种定义事件处理函数的方法方便传参
onClick={ e => { this.liClick(item, index, e) }}
title="li">
{item}
</li>
)
})
}
</ul>
</div>
)
}
btnClick(event) {
console.log("按钮发生了点击", event);
}
liClick(item, index, event) {
console.log("li发生了点击", item, index, event);
}
}
ReactDOM.render(<App/>, document.getElementById("app"));
</script>
实例:
需求: 自定义组件, 功能说明如下:
1.点击按钮, 提示第一个输入框中的值
2.当第2个输入框失去焦点时, 提示这个输入框中的值
<div id="test"></div>
<script type="text/babel">
//创建组件
class Demo extends React.Component{
//创建ref容器
myRef = React.createRef();
myRef2 = React.createRef();
//点击按钮的回调-展示左侧输入框的数据
showData = (event)=>{
console.log(event.target);
alert(this.myRef.current.value);
}
//失去焦点的回调-展示右侧输入框的数据
showData2 = (event)=>{
//发生事件的元素与要操作的元素相同,可不使用ref
alert(event.target.value);
}
render(){
return(
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'));
</script>
6.条件渲染
1.某些情况下,界面的内容会根据不同的情况显示不同的内容,或者决定是否渲染某部分内容:
- 在vue中,我们会通过指令来控制:比如v-if、v-show
- 在React中,所有的条件判断都和普通的JavaScript代码一致
2.常见的条件渲染方式
- 方式一:条件判断语句
- 适合逻辑较多的情况
- 方式二:三元运算符
- 适合逻辑比较简单
- 与运算符&&
- 适合如果条件成立,渲染某一个组件;如果条件不成立,什么内容也不渲染;
- v-show的效果
- 主要是控制display属性是否为none
实例:
<div id="app"></div>
<script type="text/babel">
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isLogin: true
}
}
render() {
const { isLogin } = this.state;
// 1.方案一:通过if判断: 逻辑代码非常多的情况
let welcome = null;
let btnText = null;
if (isLogin) {
welcome = <h2>欢迎回来~</h2>
btnText = "退出";
} else {
welcome = <h2>请先登录~</h2>
btnText = "登录";
}
return (
<div>
<div>我是div元素</div>
{welcome}
{/* 2.方案二: 三元运算符 */}
<button onClick={e => this.loginClick()}>{isLogin ? "退出" : "登录"}</button>
<hr />
<h2>{isLogin ? "你好啊, admin": null}</h2>
{/* 3.方案三: 逻辑与&& */}
{/* 逻辑与: 一个条件不成立, 后面的条件都不会进行判断了 */}
<h2>{ isLogin && "你好啊, admin" }</h2>
{ isLogin && <h2>你好啊, admin</h2> }
</div>
)
}
loginClick() {
this.setState({
isLogin: !this.state.isLogin
})
}
}
ReactDOM.render(<App />, document.getElementById("app"));
</script>
v-show的效果实现:
<script type="text/babel">
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isLogin: true
}
}
render() {
const { isLogin } = this.state;
const titleDisplayValue = isLogin ? "block": "none";
return (
<div>
<button onClick={e => this.loginClick()}>{isLogin ? "退出": "登录"}</button>
<h2 style={{display: titleDisplayValue}}>你好啊, admin</h2>
</div>
)
}
loginClick() {
this.setState({
isLogin: !this.state.isLogin
})
}
}
ReactDOM.render(<App />, document.getElementById("app"));
</script>
7. Context(了解!)
7.1 理解
- 一种组件间通信方式, 常用于【祖组件】与【孙组件】(跨级组件)间通信
7.2 使用
-
创建Context容器对象:
const XxxContext = React.createContext()
- 也可以传参对象作为默认值{name: 'admin'},name一般与传入的value对象的key相同
- 渲染子组件时,外面包裹xxxContext.Provider, 通过value属性给孙组件传递数据:
<xxxContext.Provider value={数据}>
子组件
</xxxContext.Provider>
- 孙组件读取数据:
1.第一种方式:仅适用于类组件
static contextType = xxxContext // 声明接收context
this.context // 读取context中的value数据
2.第二种方式: 函数组件与类组件都可以
- 此方式可以使用多个context嵌套
//一般使用
<xxxContext.Consumer>
{
value => ( // value就是context中的value数据
要显示的内容
)
}
</xxxContext.Consumer>
//嵌套context
<xxxContext.Consumer>
{
value => ( // value就是context中的value数据
<xxxContext.Consumer>
{
value => ( // value就是context中的value数据
要显示的内容
)
}
</xxxContext.Consumer>
)
}
</xxxContext.Consumer>
注:
- 在应用开发中一般不用context, 一般都用它的封装react插件
实例1: context一般使用
import React, { Component } from 'react'
import './index.css'
//创建Context对象
const MyContext = React.createContext()
const {Provider, Consumer} = MyContext
export default class A extends Component {
state = {username:'tom',age:18}
render() {
const {username,age} = this.state
return (
<div className="parent">
<h3>我是A组件</h3>
<h4>我的用户名是:{username}</h4>
<Provider value={{username,age}}>
<B/>
</Provider>
</div>
)
}
}
class B extends Component {
render() {
return (
<div className="child">
<h3>我是B组件</h3>
<C/>
</div>
)
}
}
/* class C extends Component {
//声明接收context
static contextType = MyContext
render() {
const {username,age} = this.context
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>我从A组件接收到的用户名:{username},年龄是{age}</h4>
</div>
)
}
} */
function C(){
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>我从A组件接收到的用户名:
<Consumer>
{value => `${value.username},年龄是${value.age}`}
</Consumer>
</h4>
</div>
)
}
实例2: 嵌套(多个)context + 设置默认值
import React, { Component } from 'react';
// 创建Context对象
const UserContext = React.createContext({
nickname: "aaaa",
level: -1
})
const ThemeContext = React.createContext({
color: "black"
})
function ProfileHeader() {
// jsx -> 嵌套的方式
return (
<UserContext.Consumer>
{
value => {
return (
<ThemeContext.Consumer>
{
theme => {
return (
<div>
<h2 style={{color: theme.color}}>用户昵称: {value.nickname}</h2>
<h2>用户等级: {value.level}</h2>
<h2>颜色: {theme.color}</h2>
</div>
)
}
}
</ThemeContext.Consumer>
)
}
}
</UserContext.Consumer>
)
}
function Profile(props) {
return (
<div>
<ProfileHeader />
<ul>
<li>设置1</li>
<li>设置2</li>
<li>设置3</li>
<li>设置4</li>
</ul>
</div>
)
}
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
nickname: "kobe",
level: 99
}
}
render() {
return (
<div>
<UserContext.Provider value={this.state}>
<ThemeContext.Provider value={{ color: "red" }}>
<Profile />
</ThemeContext.Provider>
</UserContext.Provider>
</div>
)
}
}
8.收集表单数据 - 受控组件/非受控组件
1.受控组件: 表单中输入类的DOM,随着用户的输入,就会把输入的值维护到状态中。
2.非受控组件: 如果组件中的表单数据,是通过 节点.value “现用现取”那么就是非受控组件。
实例1: 一般input使用(受控组件/非受控组件)
需求: 定义一个包含表单的组件
输入用户名密码后, 点击登录提示输入信息
//1.非受控组件
<div id="test"></div>
<script type="text/babel">
//非受控组件:如果组件中的表单数据,是通过节点.value“现用现取”那么就是非受控组件。
//创建组件
class Login extends React.Component{
handleSubmit = (event)=>{
event.preventDefault(); //阻止表单提交-阻止默认行为
const {username,password} = this;
alert(`你输入的用户名是:${username.value},你输入的密码是:${password.value}`);
}
render(){
return(
<form onSubmit={this.handleSubmit}>
用户名:<input ref={c => this.username = c} type="text" name="username"/>
密码:<input ref={c => this.password = c} type="password" name="password"/>
<button>登录</button>
</form>
)
}
}
//渲染组件
ReactDOM.render(<Login/>,document.getElementById('test'));
</script>
//2.受控组件
<div id="test"></div>
<script type="text/babel">
//受控组件:表单中输入类的DOM,随着用户的输入,就会把输入的值维护到状态中。
//创建组件
class Login extends React.Component{
//初始化状态(表单收集几个数据,我就在状态中准备几组key-value)
state = {
username:'', //存储用户名-初始值为空
password:'' //存储密码-初始值为空
}
//保存用户名到状态中-用户名发生改变的回调
saveUsername = (event)=>{
this.setState({username:event.target.value})
}
//保存密码到状态中-密码发生改变的回调
savePassword = (event)=>{
this.setState({password:event.target.value})
}
//表单提交的回调
handleSubmit = (event)=>{
event.preventDefault() //阻止表单提交
const {username,password} = this.state
alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
}
render(){
return(
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveUsername} type="text" name="username" value={this.state.username}/>
密码:<input onChange={this.savePassword} type="password" name="password" value={this.state.password}/>
<button>登录</button>
</form>
)
}
}
//渲染组件
ReactDOM.render(<Login/>,document.getElementById('test'));
</script>
实例2: select的使用
import React, { PureComponent } from 'react'
export default class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
fruits: "orange"
}
}
render() {
return (
<div>
<form onSubmit={e => this.handleSubmit(e)}>
<select name="fruits"
onChange={e => this.handleChange(e)}
//设置默认选中的项
value={this.state.fruits}>
<option value="apple">苹果</option>
<option value="banana">香蕉</option>
<option value="orange">橘子</option>
</select>
<input type="submit" value="提交"/>
</form>
</div>
)
}
handleSubmit(event) {
event.preventDefault();
console.log(this.state.fruits);
}
handleChange(event) {
this.setState({
fruits: event.target.value
})
}
}
9.高阶函数与函数柯里化
1.高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
1)若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
2)若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
常见的高阶函数有:Promise、bind、setTimeout、数组遍历相关方法,$()等等。
2.函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数,最后统一处理的函数编码形式。
function sum(a){
return(b)=>{
return (c)=>{
return a+b+c;
}
}
}
实例: 改写受控组件的案例:保存表单数据到状态中的的函数2合一
<div id="test"></div>
<script type="text/babel">
//创建组件
class Login extends React.Component{
//初始化状态
state = {
username:'', //用户名
password:'' //密码
}
//保存表单数据到状态中的函数2合一
//减少重复代码
saveFormData = (dataType)=>{
return (event)=>{
this.setState({[dataType]:event.target.value})
}
}
//写法2-不用高阶函数且不传参(利用e.target.name)
// saveFormData = (e)=>{
// // console.log(e.target.value);
// //计算属性名
// this.setState({[e.target.name]:e.target.value});
// }
//写法3-不用高阶函数,但传2个参数
//saveFormData = (dataType,event)=>{
// this.setState({[dataType]:event.target.value})
//}
//表单提交的回调
handleSubmit = (event)=>{
event.preventDefault(); //阻止表单提交
const {username,password} = this.state; //不再节点.value获取输入了,去状态中获取
alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
}
render(){
return(
// onChange={this.saveFormData('username')}
// 此处saveFormData返回的函数作为onChange的回调
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveFormData('username')} type="text" name="username"/>
密码:<input onChange={this.saveFormData('password')} type="password" name="password"/>
<button>登录</button>
</form>
//写法2
// <form onSubmit={this.handleSubmit}>
// 用户名:<input onChange={this.saveFormData} type="text" name="username"/>
// 密码:<input onChange={this.saveFormData} type="password" name="password"/>
// <button>登录</button>
// </form>
//写法3
//<form onSubmit={this.handleSubmit}>
// 用户名:<input onChange={event => this.saveFormData('username',event)} type="text" name="username"/>
// 密码:<input onChange={event => this.saveFormData('password',event)} type="password" name="password"/>
// <button>登录</button>
//</form>
)
}
}
//渲染组件
ReactDOM.render(<Login/>,document.getElementById('test'));
</script>
10.高阶组件HOC
1.定义: 参数为组件,返回值为新组件的函数
2.常见的高阶组件: AntD v3 的 Form.create()(组件) / withRouter(组件) / connect()(组件)
3.组件的本质是函数
4.与高阶函数的关系?
-
高阶组件是一个特别的高阶函数
-
接收的是组件函数, 同时返回新的组件函数
5.作用:
- React 中用于复用组件逻辑的一种高级技巧
- 对原组件进行加工处理(包裹一层),返回新组件
**实例:**antd v3 的 Form.create()(组件)
//Form.create()(Login), 接收一个Login组件, 返回一个新组件
Form.create = function () {
const form = 创建一个强大form对象
return function (FormComponent) { //此为高阶组件
return class WrapComponent extends Component {
render () {
return <Login form={form}/>
}
}
}
}
const LoginWrap = Form.create()(Login)
6.应用
1)props的增强
- 不修改原有代码的情况下,添加新的props
import React, { PureComponent } from 'react';
// 定义一个高阶组件
function enhanceRegionProps(WrappedComponent) {
return props => {
return <WrappedComponent {...props} region="中国"/>
}
}
class Home extends PureComponent {
render() {
return <h2>Home: {`昵称: ${this.props.nickname} 等级: ${this.props.level} 区域: ${this.props.region}`}</h2>
}
}
class About extends PureComponent {
render() {
return <h2>About: {`昵称: ${this.props.nickname} 等级: ${this.props.level} 区域: ${this.props.region}`}</h2>
}
}
const EnhanceHome = enhanceRegionProps(Home);
const EnhanceAbout = enhanceRegionProps(About);
class App extends PureComponent {
render() {
return (
<div>
App
<EnhanceHome nickname="admin" level={90}/>
<EnhanceAbout nickname="kobe" level={99}/>
</div>
)
}
}
export default App;
- 利用高阶组件来共享Context
//1.一般写法
import React, { PureComponent, createContext } from 'react';
// 创建Context
const UserContext = createContext({
nickname: "默认",
level: -1,
区域: "中国"
});
class Home extends PureComponent {
render() {
return (
<UserContext.Consumer>
{
user => {
return <h2>Home: {`昵称: ${user.nickname} 等级: ${user.level} 区域: ${user.region}`}</h2>
}
}
</UserContext.Consumer>
)
}
}
class About extends PureComponent {
render() {
return (
<UserContext.Consumer>
{
user => {
return <h2>About: {`昵称: ${user.nickname} 等级: ${user.level} 区域: ${user.region}`}</h2>
}
}
</UserContext.Consumer>
)
}
}
class App extends PureComponent {
render() {
return (
<div>
App
<UserContext.Provider value={{nickname: "admin", level: 90, region: "中国"}}>
<Home/>
<About/>
</UserContext.Provider>
</div>
)
}
}
export default App;
//2.高阶组件改进写法
import React, { PureComponent } from 'react';
// 定义一个高阶组件
function enhanceRegionProps(WrappedComponent) {
return props => {
return <WrappedComponent {...props} region="中国"/>
}
}
class Home extends PureComponent {
render() {
return <h2>Home: {`昵称: ${this.props.nickname} 等级: ${this.props.level} 区域: ${this.props.region}`}</h2>
}
}
class About extends PureComponent {
render() {
return <h2>About: {`昵称: ${this.props.nickname} 等级: ${this.props.level} 区域: ${this.props.region}`}</h2>
}
}
const EnhanceHome = enhanceRegionProps(Home);
const EnhanceAbout = enhanceRegionProps(About);
class App extends PureComponent {
render() {
return (
<div>
App
<EnhanceHome nickname="admin" level={90}/>
<EnhanceAbout nickname="kobe" level={99}/>
</div>
)
}
}
export default App;
2)渲染判断鉴权
- 在开发中,我们可能遇到这样的场景:
- 某些页面是必须用户登录成功才能进行进入;
- 如果用户没有登录成功,那么直接跳转到登录页面;
- 这个时候,我们就可以使用高阶组件来完成鉴权操作:
import React, { PureComponent } from 'react';
class LoginPage extends PureComponent {
render() {
return <h2>LoginPage</h2>
}
}
function withAuth(WrappedComponent) {
const NewCpn = props => {
const {isLogin} = props;
if (isLogin) {
return <WrappedComponent {...props}/>
} else {
return <LoginPage/>
}
}
//定义react浏览器调试界面的组件名称
NewCpn.displayName = "AuthCpn"
return NewCpn;
}
// 购物车组件
class CartPage extends PureComponent {
render() {
return <h2>CartPage</h2>
}
}
const AuthCartPage = withAuth(CartPage);
export default class App extends PureComponent {
render() {
return (
<div>
<AuthCartPage isLogin={true}/>
</div>
)
}
}
3)生命周期劫持
- 计算组件渲染的时间 - 性能优化
//1.一般写法
import React, { PureComponent } from 'react';
class Home extends PureComponent {
// 即将渲染获取一个时间 beginTime
UNSAFE_componentWillMount() {
this.beginTime = Date.now();
}
// 渲染完成再获取一个时间 endTime
componentDidMount() {
this.endTime = Date.now();
const interval = this.endTime - this.beginTime;
console.log(`Home渲染时间: ${interval}`)
}
render() {
return <h2>Home</h2>
}
}
class About extends PureComponent {
// 即将渲染获取一个时间 beginTime
UNSAFE_componentWillMount() {
this.beginTime = Date.now();
}
// 渲染完成再获取一个时间 endTime
componentDidMount() {
this.endTime = Date.now();
const interval = this.endTime - this.beginTime;
console.log(`About渲染时间: ${interval}`)
}
render() {
return <h2>About</h2>
}
}
export default class App extends PureComponent {
render() {
return (
<div>
<Home />
<About />
</div>
)
}
}
//2.高阶组件写法
import React, { PureComponent } from 'react';
function withRenderTime(WrappedComponent) {
return class extends PureComponent {
// 即将渲染获取一个时间 beginTime
UNSAFE_componentWillMount() {
this.beginTime = Date.now();
}
// 渲染完成再获取一个时间 endTime
componentDidMount() {
this.endTime = Date.now();
const interval = this.endTime - this.beginTime;
console.log(`${WrappedComponent.name}渲染时间: ${interval}`)
}
render() {
return <WrappedComponent {...this.props}/>
}
}
}
class Home extends PureComponent {
render() {
return <h2>Home</h2>
}
}
class About extends PureComponent {
render() {
return <h2>About</h2>
}
}
const TimeHome = withRenderTime(Home);
const TimeAbout = withRenderTime(About);
export default class App extends PureComponent {
render() {
return (
<div>
<TimeHome />
<TimeAbout />
</div>
)
}
}
class Person {
}
console.log(Person.name);
4)ref的转发 - 函数式组件使用ref,获取组件内某个元素的DOM
- 使用React.forwardRef()高阶组件
import React, { PureComponent, createRef, forwardRef } from 'react';
class Home extends PureComponent {
render() {
return <h2>Home</h2>
}
}
// 高阶组件forwardRef
const Profile = forwardRef(function(props, ref) {
return <p ref={ref}>Profile</p>
})
export default class App extends PureComponent {
constructor(props) {
super(props);
this.titleRef = createRef();
this.homeRef = createRef();
this.profileRef = createRef();
}
render() {
return (
<div>
<h2 ref={this.titleRef}>Hello World</h2>
<Home ref={this.homeRef}/>
<Profile ref={this.profileRef} name={"why"}/>
<button onClick={e => this.printRef()}>打印ref</button>
</div>
)
}
printRef() {
console.log(this.titleRef.current);
console.log(this.homeRef.current);
console.log(this.profileRef.current);
}
}