函数式组件适用于简单组件,类式组件适用于复杂组件;
什么是简单组件?什么是复杂组件?
若是组件是有状态的则被称为复杂组件
若是组件没有状态的被称为简单组件;
为什么这么说呢?
因为react的三大属性是组件实例对象的三大属性,
函数式组件是没有实例的,因此基本不使用这三大属性(后面在hooks中使用);
类式组件存在实例,这三大属性主要是基于class组件来说的
用于存储数据,状态(数据)驱动试图
初始化数据
this.state={
属性名:属性值
} // state必须为一个对象
更新值 -> 通过setState
更新值
通过setState更新数据有两种方式
第一种直接传入修改后的值
举例说明:将count的值修改为111
this.setState({count:111})
第二种方式是传入一个回调函数,函数的返回值即是要修改的值
举例说明:将count的值修改为111
this.setState({count:111})
渲染过程
使用时机
this.setState({count:111})
this.setState(()=>({count:111}))
使用函数式感觉比较繁琐const { count } = this.state
this.setState({count:count+1})
this.setState(state=>({count:state.count+1}))
使用函数式不需要再获取state数据了,因为react在调用函数时帮我们传递过来了注意:通过setState更新数据是异步的
import React, { Component } from 'react'
export default class StateDemo extends Component {
state = {
count: 1
}
editCount = ()=>{
this.setState(state=>({count:state.count+1}))
console.log('count', this.state.count)
}
render() {
return (
<div>
{this.state.count}
<button onClick={this.editCount}>点我count的值变为111</button>
</div>
)
}
}
当我第一次点击按钮时,页面显示2 但是控制台打印的却是1
this.setState(stateChange,[callback])
第二个参数是一个回调函数,该回调函数的执行时机是在 在此次数据更新的render函数调用之后执行 : setState->render -> callbackeditCount = ()=>{
this.setState(state=>({count:state.count+1}),()=>{
console.log('count', this.state.count) // 2
})
}
此时第一次点击,控制台打印的就是2了案例:默认显示文本 今天天气炎热,当点击文本时显示 今天天气凉爽
实现
<div id="test">div>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js">script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js">script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js">script>
<script type="text/babel">
class Mycomponent extends React.Component{
// 初始化state数据(若是显示定义了构造器则建议在构造器中初始化状态)
state = {isHot:false}
render(){
const {isHot} = this.state
return <h1 onClick={this.test}>今天天气{isHot ? '炎热' : '凉爽'}</h1>
}
test=()=>{
console.log(this) // 发现在实例化对象的原型对象的原型对象上会发现有一个setState方法(是React.Component上定义的方法)
// 获取状态中的数据
const {isHot} = this.state
// 通过内置API setState去修改state中的数据
this.setState({isHot:!isHot})
}
}
ReactDOM.render(<Mycomponent />, document.getElementById('test'))
script>
不是替换而是合并
<script>
let arr1 = [1, 3, 5, 7, 9]
let arr2 = [2, 4, 6, 8, 10]
// 【1】展开一个数组
console.log(...arr1);
// 【2】合并数组
let arr3 = [...arr1, ...arr2]
// 【3】在函数接收参数使用中使用
function sum(...numbers) {
return numbers.reduce((preValue, currentValue) => {
return preValue + currentValue
})
}
console.log(sum(1, 2, 3, 4));
// 【4】构造字面量对象时使用展开语法
let person = {name: 'tom', age: 18}
let person2 = {...person}
// 【5】合并修改
let person3 = {...person, name: 'jack', address: "地球"}
console.log(person3)// {address: "地球", age: 18, name: "jack"}
// 注意:展开运算符不能展开对象
//console.log(...person); //报错,
</script>
总结:
展开运算符可以用来展开数组、合并数组、构造字面量对象时使用展开语法、合并修改,但是却不可以展开对象
!
在很多时候需要将数据传入组件中,应该怎么传递呢?
属性
的形式将数据传入,会统一在该实例化对象的props属性
中进行接收。 <div id="test"></div>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<!-- 类式组件 -->
<!-- [1]以属性的形式传递数据 实例:传入姓名、性别、年龄并在页面上进行显示-->
<script type="text/babel">
class Mycomponent extends React.Component{
render(){
console.log(this.props)
const {name, age ,sex} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
</ul>
)
}
}
ReactDOM.render(<Mycomponent name='chaochao' sex='女' age='12' />, document.getElementById('test'))
</script>
属性
的形式将数据传入,会统一在方法以形参
的形式进行接收(参数为一个对象–全部参数以键值对的形式存储)<div id="test"></div>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
function Mycomponent (props){
const {name, age , sex} = props
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
</ul>
)
}
// 函数中没有静态属性,因此将限制写在函数外面
Mycomponent.protoTypes={
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.array
}
Mycomponent.defaultProps={
sex:'待定',
age:0
}
const obj ={
name:'chaochao',
// sex:'女',
age:18
}
ReactDOM.render(<Mycomponent {...obj} />, document.getElementById('test'))
</script>
只读
属性,只允许使用不允许修改在很多情况下,我们传递的数据可能是一个对象或者是属性很多,一一传递显然不是很现实,在这里可以使用展开运算符进行数据传递
<div id="test">div>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js">script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js">script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js">script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js">script>
<script type="text/babel">
class Mycomponent extends React.Component{
render(){
console.log(this.props)
const {name, age ,sex} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
</ul>
)
}
}
const obj ={
name:'chaochao',
sex:'女',
age:18
}
ReactDOM.render(<Mycomponent {...obj} />, document.getElementById('test')) // {}表示要在jsx中混入js语法了并不是对象
script>
通过上述代码 发现页面数据依旧能正常显示,但是 展开运算符不是不能够展开对象吗?
—>是因为引入的React核心语法以及babel的作用下 可以在标签间使用展开运算符展开对象(仅能在标签间使用
)
构造函数存在propTypes属性,作用是进行数据类型校验;
propTypes = {
属性名: 校验规则
}
校验规则使用的是PropTypes
对象 -> PropTypes 提供一系列验证器,可用于确保组件接收到的数据类型是有效的,出于性能方面的考虑,propTypes 仅在开发模式下进行检查。
因此在15.5版本之后 再使用PropTypes就需要先安装 prop-types 包;
这里是一个示例记录提供的不同的验证器:
import PropTypes from 'prop-types';
MyComponent.propTypes = {
// 你可以声明一个 prop 是一个特定的 JS 原始类型。
optionalArray: PropTypes.array, // 数组
optionalBool: PropTypes.bool, // boolean值
optionalFunc: PropTypes.func, // 函数
optionalNumber: PropTypes.number, // 数字
optionalObject: PropTypes.object, // 对象
optionalString: PropTypes.string, // 字符串
optionalSymbol: PropTypes.symbol, // symbol
optionalElement: PropTypes.element,// React 元素。
// 你也可以声明一个 prop 是类的一个实例。
optionalMessage: PropTypes.instanceOf(Message),
// 你可以声明 prop 是特定的值,类似于枚举
optionalEnum: PropTypes.oneOf(['News', 'Photos']),
// 一个对象可以是多种类型其中之一
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
// 你可以使用 `isRequired' 链接上述任何一个,以确保在没有提供 prop 的情况下显示警告。
// 函数类型的值且必填
requiredFunc: PropTypes.func.isRequired,
// 任何数据类型的值但必填
requiredAny: PropTypes.any.isRequired,
// 你也可以声明自定义的验证器。如果验证失败返回 Error 对象。不要使用 `console.warn` 或者 throw ,
// 因为这不会在 `oneOfType` 类型的验证器中起作用。
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
return new Error(
'Invalid prop `' + propName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
},
// 也可以声明`arrayOf`和`objectOf`类型的验证器,如果验证失败需要返回Error对象。
// 会在数组或者对象的每一个元素上调用验证器。验证器的前两个参数分别是数组或者对象本身,
// 以及当前元素的键值。
customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
if (!/matchme/.test(propValue[key])) {
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
})
};
<div id="test"></div>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<!-- 【3】对传入的数据进行类型限制(数据类型、是否必传、设置默认值) 实例:传入姓名、性别、年龄并在页面上进行显示-->
<script type="text/babel">
class Mycomponent extends React.Component{
static propTypes={
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number
}
render(){
const {name, age ,sex} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
</ul>
)
}
}
const obj ={
name:'chaochao',
sex:'女',
age:'18'
}
ReactDOM.render(<Mycomponent {...obj} />, document.getElementById('test'))
</script>
构造函数存在defaultProps属性,作用是设置数据的默认值
defaultProps={
属性名:'默认值'
}
<div id="test"></div>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<!-- 【3】对传入的数据进行类型限制(数据类型、是否必传、设置默认值) 实例:传入姓名、性别、年龄并在页面上进行显示-->
<script type="text/babel">
class Mycomponent extends React.Component{
static defaultProps={
sex:'待定',
age:0
}
render(){
const {name, age ,sex} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
</ul>
)
}
}
const obj ={
name:'chaochao',
age:18
}
ReactDOM.render(<Mycomponent {...obj} />, document.getElementById('test'))
</script>
在react中尽量不要直接操作dom,如果要操作dom,可以使用refs;
定义ref的形式有3种
每个实例化对象上都存在一个refs属性,该属性为一个对象;
在想要获取的vdom上添加ref属性,属性值为一个字符串;
添加之后就会将该dom元素以键值对的形式(ref属性名:dom元素)添加在refs对象中;
<div id="test"></div>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
class Mycomponent extends React.Component{
state = {isHot:false}
render(){
const {isHot} = this.state
return (
<div>
<input ref='input1' type="text" />
<button onClick={this.getValue}>点我显示数据</button>
</div>
)
}
getValue = ()=>{
console.log('refs', this.refs) // refs {input1: input}
const {input1} = this.refs
console.log(input1.value) // 输入111点击按钮->111
}
}
ReactDOM.render(<Mycomponent />, document.getElementById('test'))
</script>
该形式在官方不推荐使用,因为使用此方式定义ref若是多次定义—效率有很大的问题
1.定义了函数;
2.函数自己没调用;
3.函数最终被执行了
在想要获取的vdom上添加ref属性, 属性值为一个函数
在render函数执行过程中,发现ref的属性值为一个函数,react就会调用该函数并将该dom作为参数传入到函数中,我们就可以拿到该dom了。
ref={dom => value=dom}
<div id="test"></div>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
class Mycomponent extends React.Component{
state = {isHot:false}
render(){
const {isHot} = this.state
return (
<div>
<input ref={c => this.input=c} type="text" />
<button onClick={this.getValue}>点我显示数据</button>
</div>
)
}
getValue = ()=>{
const {input} = this
console.log(input.value) // 输入111点击按钮-> 111
}
}
ReactDOM.render(<Mycomponent />, document.getElementById('test'))
</script>
问题
当将函数写在行内时
解决
不直接将函数写在行内,将函数写在实例对象上之后引入就不会有这样的问题;
验证
<div id="test"></div>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
class Mycomponent extends React.Component{
state = {isHot:false}
render(){
return (
<div>
<input ref={c => {console.log('c',c); this.input=c}} type="text" />
<button onClick={this.getValue}>点我显示数据</button>
<button onClick={this.editValue}>点我更改state</button>
</div>
)
}
getValue = ()=>{
const {input} = this
console.log(input.value) // 输入111点击按钮-> 111
}
editValue = ()=>{
const {isHot} = this.state
this.setState({isHot: !isHot})
}
}
ReactDOM.render(<Mycomponent />, document.getElementById('test'))
</script>
<div id="test"></div>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
class Mycomponent extends React.Component{
state = {isHot:false}
render(){
return (
<div>
<input ref={this.reffun} type="text" />
<button onClick={this.getValue}>点我显示数据</button>
<button onClick={this.editValue}>点我更改state</button>
</div>
)
}
reffun = (dom)=>{
console.log('c',dom);
this.input=dom
}
getValue = ()=>{
const {input} = this
console.log(input.value) // 输入111点击按钮-> 111
}
editValue = ()=>{
const {isHot} = this.state
this.setState({isHot: !isHot})
}
}
ReactDOM.render(<Mycomponent />, document.getElementById('test'))
</script>
将函数写在行内—初始渲染该函数执行1次,每次更新执行两次;
不将函数写在行内—只会在初始化时执行1次;
React对象上存在一个createRef
方法,该方法调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”的->也就是说使用该方法产生的容器只能装一个dom;
this.input = React.createRef() // 产生一个容器
<input ref={ this.input }> // 存储
const {current} = this.input // 容器的current属性表示当前的dom元素
<div id="test"></div>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
class Mycomponent extends React.Component{
state = {isHot:false}
input1 = React.createRef()
render(){
const {isHot} = this.state
return (
<div>
<input ref={this.input1} type="text" />
<button onClick={this.getValue}>点我显示数据</button>
</div>
)
}
getValue = ()=>{
const {current} = this.input1
console.log(current.value) //输入111点击按钮->111
}
}
ReactDOM.render(<Mycomponent />, document.getElementById('test'))
</script>