注:学习此篇内容一定要先把类的基本知识搞懂,可参考我另一篇文章: 三分钟让你搞懂Class类 - 掘金 (juejin.cn)
本文是对网站免费资源的知识总结,如侵联删。
函数式组件的定义
废话不多说,直接上例子:
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入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>
<script type="text/babel">
//1.创建函数式组件
function MyComponent(){
console.log(this); //此处的this是undefined,因为babel编译后开启了严格模式
return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}
//2.渲染组件到页面
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
</script>
执行了ReactDOM.render(.......之后,发生了什么?
- React解析组件标签,找到了MyComponent组件。
- 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。
类式组件的定义
废话不多说,直接上例子:
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入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>
//1.创建类式组件
<script type="text/babel">
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>
- 如果你了解Class的相关知识,你就能知道render其实就是MyComponent实例原型对象上的一个方法而已,那么this自然就执行MyComponent组件实例对象
- 渲染组件时,React做了什么:
- React解析组件标签,找到了MyComponent组件。
- 发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
- 将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
组件实例三大核心属性之一:state状态
如果你的组件是有状态(state)的,就是复杂组件,反之,就是简单组件
state就是数据,不管是Vue还是React,都是靠数据驱动视图,只不过底层使用了响应式原理和自动更新视图
因为函数式组件是没有this的,因为函数式组件中采用了严格模式,而严格模式下没有this,就别谈组件实例,更别谈组件实例的核心属性了
只有类式组件有,但是后来react增加了hooks,函数式组件也有了状态
state中的this问题(重要)
那么state在哪里呢?其实React已经给我们封装好了,我们在render中打印this会发现在组件的实例对象中有context、props、refs、state等初始值
如果我们需要对类的实例化对象进行一些初始化操作,如增加一个属性,修改一个属性的值,需要借助类的构造器
写 this.xxx=xxx即可
案例:页面展示天气炎热或凉爽的文字,当点击文字时,改变文字内容
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入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>
<script type="text/babel">
//1.创建组件
class Weather extends React.Component{
//构造器调用几次? ———— 1次
constructor(props){
super(props)
//初始化状态
this.state = {isHot:false,wind:'微风'}
}
render(){
//读取状态
const {isHot,wind} = this.state
return <h1>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
}
}
//2.渲染组件到页面
ReactDOM.render(<Weather/>,document.getElementById('test'))
</script>
</body>
到这里,都没什么难度,接下来我们要给这段文字添加一个点击事件,当点击文字的时候,文字发生改变,在做该操作之前,我们先来解决React中的小问题
小细节
我们把代码撰写如下:给文字加一个click事件,按理说这样没问题,但是我们看效果
<body>
<script type="text/babel">
//1.创建组件
class Weather extends React.Component{
constructor(props){
super(props)
this.state = {isHot:false,wind:'微风'}
}
render(){
const {isHot,wind} = this.state
return <h1 onClick={demo()}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
}
}
//2.渲染组件到页面
ReactDOM.render(<Weather/>,document.getElementById('test'))
function demo(){
console.log('我被点击了');
}
</script>
</body>
这说明你不是把demo函数赋给了点击事件,而是把demo函数运行完的返回值赋给了点击事件,由于demo函数中没有return 任何东西,因此就是undefined,因此不生效,此处小细节需要注意下。 我们只需要这样写,就可以了:
return <h1 onClick={demo}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
添加点击事件
this的指向问题
我们加一个changeWeather事件,如下所示:
<script type="text/babel">
//1.创建组件
class Weather extends React.Component{
constructor(props){
super(props)
this.state = {isHot:false,wind:'微风'}
}
render(){
const {isHot,wind} = this.state
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
}
changeWeather(){
console.log(this.state.isHot)
}
}
//2.渲染组件到页面
ReactDOM.render(<Weather/>,document.getElementById('test'))
</script>
而当我们这样写完代码,发现竟然报错了,这里说明this是undefined,这是为何呢?
我们在changeWeather中打印this,发现真的是undefined,因为这个changeWeather不是通过组件实例调用的
解决方案1:
其实加一行代码就能解决,我们这么加
解决方案2:
基于解决方案1,我们可得到最终的解决方案2
因此最后精简之后的代码为:
<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})
}
}
//2.渲染组件到页面
ReactDOM.render(<Weather/>,document.getElementById('test'))
</script>
state中setState
在上面的案例中,我们不能直接修改isHot的数据,比如:this.state.isHot=!this.state.isHot,这样写了之后,数据虽然发生了变化,但是视图并没有更新,这是因为没有触发React的响应式原理,因此要借助setState来写,即:this.setState({isHot:!isHot})
state总结
核心
1. state是组件对象最重要的属性, 值是对象(可以包含多个key-value的组合)
2. 组件被称为"状态机", 通过更新组件的state来更新对应的页面显示(重新渲染组件)
难点
1. 组件中render方法中的this为组件实例对象
2. 组件自定义的方法中this为undefined,如何解决?
a) 强制绑定this: 通过函数对象的bind()
b) 箭头函数
3. 状态数据,不能直接修改或更新
组件的三大核心属性之二:props
props的基本使用
<!-- 准备好一个“容器” -->
<div id="test1"></div>
<!-- 引入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>
<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel">
//创建组件
class Person extends React.Component{
render(){
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为数值
speak:PropTypes.func,//限制speak为函数
}
//指定默认标签属性值
Person.defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
//渲染组件到页面
ReactDOM.render(<Person name={100} speak={speak}/>,document.getElementById('test1'))
function speak(){
console.log('我说话了');
}
</script>
- props的使用比较简单,就是给组件身上传递属性,比如name={100}即可,然后在组件内部就可以通过this.props接收到props,之后解构出来使用即可
- 如何给props规范类型呢?这里我们需要给类的身上添加propTypes,注意,是给类本身加,而不是在类中写this.propTypes=xxx,如果是给类本身加,那么我们可以通过Person.propTypes来直接访问,如果是给类的实例对象加,那么必须先new出一个实例对象,之后再使用
- 基于第二点,想给类自身加属性,类提供了一个static方法,因此第二点可简写如下:
<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>
类式函数中的构造器和props
Class规定:如果在子类中写了构造器,且子类需要继承父类的某些属性,那么子类中必须写构造器并调用super;当然,如果子类不写构造器,那也就不需要调用super
我们看下面的案例
<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>
这个案例就谨遵了Class的知识,因为子类中写了构造器,且接收到了props,那么必须调用super
那如果我们做如下操作
constructor(){
//构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props
// console.log(props);
super()
console.log('constructor',this.props);
}
如果我在构造器中不接收props,那么页面仍然能正常渲染,但是打印出来就是undefined
我们可以看官网的解释
如文章对state的分析对于第一种情况,我们可以直接写state={}来解决,这样就不需要写构造函数;
对于第二种情况,是为了给函数绑定this,也就是上文中的解决方案1,但是也可以通过解决方案2来解决
因此我们尽量避免在构造器中写super(props),除非你希望在构造器中通过this访问到props
函数式组件中的props
对于函数式组件,虽然没有this,也没有构造函数,但是函数本身是可以接收到形参的,因为函数虽然不能玩state,refs,但是可以玩props等以后有了hooks,函数式组件也都能玩
咱们写一个案例来说明函数式组件是如何玩props的
<script type="text/babel">
//创建组件
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('test1'))
</script>
组件的三大核心属性之三:refs
理解:组件内的标签可以定义ref属性来标识自己,和Vue类似,这里不再做过多阐述
<script type="text/babel">
//创建组件
class Demo extends React.Component{
//展示左侧输入框的数据
showData = ()=>{
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>