目录
(4-3)解决类中 this 在非实例调用时为 undefined 的问题 AND 完善事件处理 AND 更新state对象中的属性并重新渲染
1、函数式组件的简单使用
// 声明一个简单的函数式组件,返回一段简单的jsx标签内容
// 函数组件内的 this 指向的是undefined,因为babel编译后开启了严格模式
const TextComponent = ()=>{
return <h1>简单函数组件的使用</h1>
}
// 渲染到页面中
ReactDOM.render(<TextComponent/>, document.querySelector('#test'));
// 使用组件时,第一个参数要写的是组件标签的形式,
// 首字母要大写,标签要闭合,标签名与对应的函数组件的函数名一致
// React 会调用该函数将返回的虚拟dom转换成真实的dom,然后渲染到页面中
2、类式组件的简单使用
// 创建类式组件(类名首字母要大写),要继承 React.Component
class MyComponent extends React.Component{
// 必须要有一个render实例方法,该方法返回需要渲染的内容
render(){
return <h1>这是一个类组件</h1>
}
}
// React 解析组件标签,会根据名称找到对应的类,然后会构造出实例,
// 并调用其render方法,获得虚拟dom,然后再转换成真实dom然后再渲染到页面上
ReactDOM.render(<MyComponent/>,document.querySelector('#test'));
3、关于JavaScript 类 的简单复习
// 创建一个 person 类
class Person{
// 构造函数方法
constructor(name, age){
// 添加到 this 上的所有内容都会作用到不同的实例身上
this.name = name;
this.age = age;
}
// 类中可以有构造器、方法、赋值语句(赋值语句不用声明类型)
gender = 'man'
// 实例方法 定义在类的原型对象上
speak(){
console.log(`my name is ${this.name}, I am ${this.age} year old.`);
}
// 类的静态方法 定义到类的身上,要用 类名.静态方法名 调用
static allSpeak(){
console.log('person');
}
defToString(str){
console.log(str);
}
}
// 创建一个子类 Student 继承 Person
class Student extends Person{
constructor(name, age, grade){
super(name, age); // 调用父类的构造方法 如果要使用,要写在构造器的最前面
this.grade = grade;
}
// 重写父类的speak方法
speak(){
console.log(`I am a student,I'm in grade ${this.grade}.`);
}
// 子类自己的实例方法
defaultSpeak(){
super.speak(); // 调用父类原来的speak方法
}
setName(name){ // 修改名字
this.name = name;
}
// 在子类的静态方法中调用父类的静态方法
static staticCallFatherAllSpeak(str){
console.log('Student类静态方法中调用父类的静态方法 allSpeak():');
super.allSpeak();
}
// 在子类的实例方法中调用父类的静态方法
callFatherAllSpeak(str){
// super.allSpeak(); // 错误
Person.allSpeak(); // 只能这样
}
}
// 创建一个 Student类 的实例化对象
// 类实例化时传入的参数会作为构造函数的参数。如果不需要参数,则类名后的括号也可省略,如 const s = new Student;
const s1 = new Student('张三', 12, 6);
s1.speak(); // 因为子类重写了父类的speak方法,则调用的是子类重写的方法
s1.defaultSpeak();
// s1.allSpeak(); // 错
Person.allSpeak(); // 静态方法要这样调用
s1.defToString(s1.name);
s1.setName('李明');
s1.defToString(s1.name);
s1.callFatherAllSpeak('调用父类的静态方法 allSpeak()');
4、类组件实例属性—— state
(4-1)state基本使用 AND 绑定事件
创建一个天气类 Weather,该类继承自 React.Component,则该类的实例身上会有 props、refs、state等的属性。
state 作为一个对象在我们的项目中使用,用于存储类组件内部的数据。
在类Weather 的构造器中,使用this.state 可以获取到 state。初始时state 为 null,我们在构造器中将 state 定义为对象类型,并为其指定若干属性。
// 创建一个天气类
class Weather extends React.Component{
// 可以通过props访问外部的数据
constructor(props){
super(props);
this.state = { // state为一个对象,储存类内部数据
isHot: false // 天气是否炎热
};
}
// 当组件的状态数据(state)改变,会自动调用此方法渲染对应的标记
render(){
// 事件绑定的事件名用驼峰写法,on后面的首字母变成大写,如onblur => onBlur
// 等号右边写一对花括号加函数名,不要加圆括号,加了就会在渲染时立即执行,且会把函数的返回值给onClick
// 如果函数没有返回值,则把undefined返回给onClick作为回调,对应的事件没有任何效果产生
return (
<h2 onClick={changeWeather}>The weather of today is {this.state.isHot ? 'hot' : 'cool'}.</h2>
)
}
}
ReactDOM.render(<Weather/>, document.querySelector('#test'));
// 事件处理
function changeWeather(){
console.log('click');
}
/**
* 如果在render方法返回的h2中,绑定点击事件写成 onClick={changeWeather()}
* 即加了一个括号,而changeWeather函数如上不做更改,则页面加载完后输出一个 “click”,此后点击h2没有任何回应,
* 因为没有返回任何东西给onClick
* 如果changeWeather函数的函数体改为 return console.log("click");
* 则把 console.log("click") 返回给onClick,则每次点击h2控制台都会输出 click
*/
(4-2)类中方法的 this 指向问题背景
class Weather extends React.Component{
constructor(props){
super(props);
this.state = {
isHot: false
};
}
render(){
return (
/*
onClick={this.changeWeather} ,相当于把changeWeather这个函数赋给了onClick
当点击h2内容时,虽然也调用了该changeWeather方法,但是不是实例调用的,方法中的this指向不会是类的任何一个实例
因为类中的方法默认开启了严格模式,则this将会是undefined
*/
<h2 onClick={this.changeWeather}>The weather of today is {this.state.isHot ? 'hot' : 'cool'}.</h2>
/**
* 此时的调用相当于:
* const w = new Weather();
* const clickEvent = w.changeWeather;
* clickEvent(); // 这样调用
*/
)
}
changeWeather(str){
// 如果是通过类的实例调用,则该this就指向该类的实例对象,不管经过多少个中间层(即通过类中的其他方法再调用该方法
// 不管如何,只要不是类的实例调用的,这里的this都会指向undefined
console.log(str,this);
}
test(str){ // 该方法作为中间层测试,调用 changeWeather 方法
this.changeWeather(str);
}
}
ReactDOM.render(<Weather/>, document.querySelector('#test'));
const w = new Weather();
w.changeWeather('直接调用');
w.test('通过 实例对象.类中的另一个函数 再调用changeWeather()');
控制台输出的两条结果是一样的:
而此时点击h2元素,this 的指向是undefined。(第一个对象是所触发的事件对象)
(4-3)解决类中 this 在非实例调用时为 undefined 的问题 AND 完善事件处理 AND 更新state对象中的属性并重新渲染
在类的构造器中,让目标方法调用 bind() 方法,修改目标方法的 this 指向,并把返回的新函数赋给一个实例方法。需要使用到该目标方法的地方不在调用该目标方法,转而调用的是新的这个实例方法。
class Weather extends React.Component{
constructor(props){
super(props);
this.state = {
isHot: false
};
// 解决非实例对象调用类中方法时,类中方法的 this 的指向问题
// function.bind(obj) bind方法调用后返回一个新的函数,该函数中的this将是所指定的第一个参数
this.selfChangeWeather = this.changeWeather.bind(this);
// 创建实例时 this.changeWeather 方法往上级的父类中逐层查找
// this.selfChangeWeather 方法是为该实例创建的,将存在于实例自身
}
render(){
return (
// 事件绑定的是经过修改 this 指向的 selfChangeWeather 方法,而不是 changeWeather
<h2 onClick={this.selfChangeWeather}>The weather of today is {this.state.isHot ? 'hot' : 'cool'}.</h2>
)
}
changeWeather(){
// 处理事件
// this.state.isHot = !this.state.isHot; // state中的属性值直接改是无效的,写法错误
/**
* 应调用React.compontent 中的setState方法来修改
* 传入一个对象作为参数,对象里的属性是需要修改的属性,值是要改成的新值
* 是关于原 state 的合并,不会影响其他属性
*/
this.setState({isHot:!this.state.isHot});
}
}
ReactDOM.render(<Weather/>, document.querySelector('#test'));
在控制台中创建一个 Weather 类的实例,可查看或验证 selfChangeWeather 和 changeWeather 的所在位置。
(4-4)state简化的写法
class Weather extends React.Component{
state = { // 直接在类中给 state 赋值
isHot:false,
}
render(){
return (
<h2 onClick={this.changeWeather}>The weather of today is {this.state.isHot ? 'hot' : 'cool'}.</h2>
)
}
// 这个自定义方法也是一个赋值语句
// 箭头函数没有自己的this,this会指向外层,则函数内的this指向的就是这个类的实例
changeWeather = ()=>{
this.setState({isHot:!this.state.isHot});
}
}
ReactDOM.render(<Weather/>, document.querySelector('#test'));
(4-5)setState修改状态的两种写法
setState 用来更新 state 中的内容,是一个异步函数。
第一种写法:this.setState(obj: stateChange,[func: callback])
state = {
count: 1
}
// 在页面渲染完毕的时候执行
componentDidMount(){
this.setState({count: this.state.count + 1}, ()=>{
// 在更新完count和重新渲染完页面之后,查看count的值
console.log('1:', this.state.count);
})
console.log('2:', this.state.count);
}
// 最后输出的结果是
// 2: 1
// 1: 2
第二种写法:this.setState(func: stateChangeFunc,[func: callback])
第一个参数是一个函数,这个函数会接收到两个参数:state 和 props;这个函数需要返回一个状态更新对象,来修改state。
state = {
count : 1
}
componentDidMount(){
this.setState((state, props)=>{
return {count: state.count +1}
},()=>{
console.log(this.state);
})
}
5、类组件实例属性—— props
(5-1)props基本使用
在组件标签中设置属性并赋值,将会添加到props对象中,在组件类中就能使用this.props.属性名来获取到对应属性的值。
注意,props 是只读的,在类中不允许修改。
class City extends React.Component {
constructor(props){
// 如果不将接收到的props传递给super,则在构造器中使用this.props的结果可能是undefined
super(props);
}
render(){
// 拿到 props 中想要的属性,并用其渲染
// 如果在组件标签中没有设置规定的属性,就会报错,页面渲染不了
let {province, childrenCity} = this.props;
return (
<div>
<h3>{ province }</h3>
<ul>
{
childrenCity.map((e, index)=>{
return (
<li key={index}>{ e }</li>
)
})
}
</ul>
</div>
)
}
}
let province = '广东';
let childrenCity = ['广州','佛山','珠海'];
// 在组件标签中设置的属性会添加到props对象中
ReactDOM.render(<City province={province} childrenCity={childrenCity}/>,
document.getElementById('test'));
(5-2)props 简写方式
class City extends React.Component {
render(){
// 拿到 props 中想要的属性,并用其渲染
// 如果在组件标签中没有设置规定的属性,就会报错,页面渲染不了
let {province, childrenCity} = this.props;
return (
<div>
<h3>{ province }</h3>
<ul>
{
childrenCity.map((e, index)=>{
return (
<li key={index}>{ e }</li>
)
})
}
</ul>
</div>
)
}
}
let province = '广东';
let childrenCity = ['广州','佛山','珠海'];
// 合并成一个对象
let cityObj = {province, childrenCity};
// 在组件标签中设置的属性会添加到props对象中
// ReactDOM.render(<City province={province} childrenCity={childrenCity}/>, document.getElementById('test'));
ReactDOM.render(<City {...cityObj}/>, document.getElementById('test'));
应该注意的是 ...cityObj 并不能对一个对象进行展开,因为 ... 运算符用于展开可迭代的元素,对象默认是不可迭代的。
而在 js 中,可以使用 {...cityObj} 可以完成对一个对象的深拷贝(原对象中属性发生改变,拷贝出来的对象中对应的属性不变)。
但是在这里(组件标签中),我们写的不是 js,而是 jsx ,{...cityObj} 这对花括号是 jsx 中书写表达式或变量的一个语法,我们真实书写的只是 ...cityObj ,并不是把一个拷贝出来的对象加在组件标签身上。
而之所以这样写,能够将这个对象中的属性和值对应地添加到 props 身上,是由 babel 和 React 帮我们解决的。
(5-3)对组件标签中设置的属性值做限制(16版本后写法)
引入一个库(prop-types)用于做 props 的限制,将得到一个全局对象 PropTypes
<script src="https://unpkg.com/prop-types@15.6/prop-types.js"></script>
class Person extends React.Component{
/**
* 第一种写法,写在定义类的内部
* 用static 修饰作为静态变量,直接添加到类的身上(直接赋值没有static 修饰的变量将存在于实例的身上
*/
// 对变量的类型以及必要性的限制写在类中 propTypes对象中(规定
static propTypes = {
name:PropTypes.string.isRequired, // 字符串类型,必须
age:PropTypes.number, // 数值类型
speak:PropTypes.func // 函数类型
}
// 对变量进行默认值的设置,写在类中 defaultProps 对象中(规定
static defaultProp = {
gender:'man',
age:18
}
render(){
const {name, age, gender} = this.props;
return (
<div>
<div>name: {name}</div>
<div>age: {age}</div>
<div>gender: {gender}</div>
</div>
)
}
}
/* 第二种写法,写在定义类的外部 */
/* 对变量的类型以及必要性的限制写在类中 propTypes对象中(规定 */
// Person.propTypes = {
// name:PropTypes.string.isRequired, // 字符串类型,必须
// age:PropTypes.number, // 数值类型
// speak:PropTypes.func // 函数类型
// }
/* 对变量进行默认值的设置,写在类中 defaultProps 对象中(规定 */
// Person.defaultProp = {
// gender:'man',
// age:18
// }
function speak(){
console.log("Hello");
}
// 如果属性是字符串,可以直接用引号的方式赋值
// 如果是数值类型(age)或者是函数类型(speak)的,需要用一对花括号进行包裹
ReactDOM.render(<Person name='ben' age={23} gender='man' speak={speak}/>,
document.querySelector('#test'));
6、函数式组件使用 props
function Person(props){
return (
<div>
<div>name: {props.name}</div>
<div>age: {props.age}</div>
<div>gender: {props.gender}</div>
</div>
)
}
Person.propTypes = {
name:PropTypes.string.isRequired,
age:PropTypes.number
}
Person.defaultProps = {
gender:'man'
}
ReactDOM.render(<Person name={"张三"} age={21}/>, document.querySelector('#test'));
7、类组件实例属性—— refs
在 JSX 标签中,用 ref = ‘xxx’ 来标识自己。在类的方法中可以使用 this.refs.xxx 获取到这个标签元素,获取到的是真实的 DOM 元素。
不应该过度地去使用 ref 。如事件绑定在目标元素A中,需要获得触发事件的目标元素A,则回调函数中的第一个参数即是目标DOM元素,不必再使用 ref 去获取。
(7-1)String 形式(过时的API,面临移除风险)
class InputShow extends React.Component{
state = {
inpval:'' // 预设 inpval 的值为空
};
render(){
return(
<div>
{ /*(jsx中的注释可以这样写) 当input失去焦点时,收集input中输入的内容,并展示到h1中*/ }
<input ref='inp' onBlur={this.showInp} type="text"/>
<h1>{this.state.inpval}</h1>
</div>
)
}
showInp = ()=>{
// 失去焦点后更新 inpval 的值,更新后,会自动再次渲染页面
this.setState({
inpval:this.refs.inp.value
})
}
}
ReactDOM.render(<InputShow/>, document.querySelector("#test"));
(7-2)使用内联回调函数形式
使用内联函数的形式,在更新的时候该函数会执行两次,第一次执行传递的参数是null,用户清空旧的 ref,然后再执行一次,这次传递的参数是当前的DOM结点,用于设置新的 ref。
可以用类绑定函数的形式来解决这个问题,但是在大多数的时候是无关紧要的。
class InputShow extends React.Component{
state = {
inpval:'' // 预设 inpval 的值为空
};
render(){
return(
<div>
{
// ref 绑定一个回调函数,每次渲染的时候这个回调函数都会被执行
// 该回调函数接收到一个参数为当前的结点 currentNode
// 利用回调函数将当前结点赋给类实例对象中的变量inpNode
}
<input ref={(cn) => {this.inpNode = cn}} onBlur={this.showInp} type="text"/>
<h1>{this.state.inpval}</h1>
</div>
)
}
showInp = ()=>{
// 失去焦点后更新 inpval 的值,更新后,会自动再次渲染页面
this.setState({
inpval:this.inpNode.value // 从实例身上去找这个结点
})
}
}
ReactDOM.render(<InputShow/>, document.querySelector("#test"));
(7-3)使用类绑定函数的形式
class InputShow extends React.Component{
state = {
inpval:'' // 预设 inpval 的值为空
};
render(){
return(
<div>
<input ref={this.setInpNode} onBlur={this.showInp} type="text"/>
<h1>{this.state.inpval}</h1>
</div>
)
}
setInpNode = (node)=>{ // 定义在类中的函数,用于绑定结点
this.inpNode = node;
}
showInp = ()=>{
// 失去焦点后更新 inpval 的值,更新后,会自动再次渲染页面
this.setState({
inpval:this.inpNode.value // 从实例身上去找这个结点
})
}
}
ReactDOM.render(<InputShow/>, document.querySelector("#test"));
(7-4)使用 React.createRef()
class ShowInfo extends React.Component{
inpA = React.createRef(); // 对应只能绑定一个结点
inpB = React.createRef();
render(){
return (
<div>
<input ref={this.inpA} onBlur={this.showInpA}
style={{backgroundColor:'yellow'}} type="text"/>
<input ref={this.inpB} onBlur={this.showInpB}
style={{border:'2px solid pink'}} type="text"/>
</div>
)
}
showInpA = (e)=>{
// 失去焦点的回调函数会接收到一个参数,即所触发的事件对象e
// e.target 即触发该事件的对象,
// 使用 this.inpA.current 获取到该Dom结点
console.log(e.target === this.inpA.current);
}
showInpB = ()=>{
console.log('B');
}
}
ReactDOM.render(<ShowInfo/>, document.getElementById('test'));