组件的三大核心属性
1:state
1)基础版本的类初始化状态
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>组件绑定事件</title>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
</head>
<body>
<div id="example">
</div>
<script type="text/babel">
// 1: 创建类式组件
class MyComponent extends React.Component {
constructor(props) {
super(props)
console.log('this==',this)
this.state = {isFlag:true}
this.handleFn = this.handleFn.bind(this)
}
render() {
let {isFlag} = this.state
return <h1 onClick={this.handleFn}>我是类式组件:{isFlag ? '状态值为真':'状态值为假'}</h1>
}
handleFn() {
let {isFlag} = this.state
this.setState({
isFlag: !isFlag
})
}
}
ReactDOM.render(
<MyComponent/>,
document.getElementById('example')
);
function handleFn () {
}
</script>
</body>
</html>
2)简约版本的类初始化状态
这样就能实现状态的更改了,但是一般在真实的项目开发中,不应该使用构造函数的形式初始化类,我们可以通过在类中写赋值语句达到初始化的效果。
更改代码如下:
class MyComponent extends React.Component {
// constructor(props) {
// super(props)
// console.log('this==',this)
// this.state = {isFlag:true}
// }
// 可以不使用构造函数,而是在类中写赋值语句,值在实例本身,注意不是类的原型上
state = {isFlag:true}
render() {
let {isFlag} = this.state
return <h1 onClick={this.handleFn}>我是类式组件:{isFlag ? '状态值为真':'状态值为假'}</h1>
}
// 定义函数
handleFn = ()=> {
// handleFn放在MyComponent的原型对象上,供实例使用
// 由于handleFn是作为onClick的回调,而不是通过实例调用的,所以是直接调用
// 类中的方法开启了严格模式,改变了this指向,所以handleFn中的this为undefined
// 解决办法是:使用箭头函数
let {isFlag} = this.state
this.setState({
isFlag: !isFlag
})
}
}
注意
- 为了初始化类的状态,我们使用了构造函数的方式,即基础版的方式。
constructor(props) {
super(props)
console.log('this==',this)
this.state = {isFlag:true}
this.handleFn = this.handleFn.bind(this)
}
但定义类状态的时候,有些值不是从外部传进去的,那么此时我们可以不需要构造函数。
即不是从外部传进去的参数,我们可以不用定义在构造函数中。由于isFlag不是从外部传递进去的,我们可以写在构造函数外部:
// 可以不使用构造函数,而是在类中写赋值语句,值在实例本身,注意不是类的原型上
state = {isFlag:true}
- 我们在类中定义的函数,都是作为事件回调来使用的。
自定义的函数一般都是作为回调函数使用的,由于类使用的是严格模式,改变类this,即this = undefined;又由于类中自定义的函数是挂在在类的原型上,供类的实例调用;就是所只有类的实例才能调用自定义的函数,而此时this = undefined不是类的实例,所以我们需要在构造函数中手动改变this的指向,所以就有了如下代码:
constructor(props) {
super(props)
console.log('this==',this)
this.state = {isFlag:true}
this.handleFn = this.handleFn.bind(this) // 改变this指向问题
}
handleFn() {
let {isFlag} = this.state
this.setState({
isFlag: !isFlag
})
}
通过下图可以看出,handleFn函数在类的原型上的
就是说,我们自定义的函数要想能够供类的实例使用,这些函数都需要通过bind改变
this指向问题。那么问题来了,假如我们有100个自定义函数,这100个函数要想被类的实例对象使用,那么它们都改变this指向问题,这样太麻烦了,于是就有了如下精简办法。
handleFn = ()=> {
let {isFlag} = this.state
this.setState({
isFlag: !isFlag
})
}
通过下图可以看出,handleFn函数不在类的原型上的,而是在类的自身
由于this = undefined问题,我们使用了bind的方式改变了this指向问题,我们最终就是想拿得到类的实例对象,那么什么方式可以拿到this呢?箭头函数可以拿到它所处环境的this;为什么自定义函数要用赋值语句的方式呢?那是为了能够使用箭头函数才用的,因为直接定义函数的方式不能够使用箭头函数,即:
handleFn ()=> {} // 这是错误的写法,不存在这种写法
2:props
1)基础版本
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>props</title>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
</head>
<body>
<div id="example">
</div>
<div id="example2">
</div>
<script type="text/babel">
let listData1 = {
name: '张三',
age: `20`,
sex: '男'
}
let listData2 = {
name: '李四',
age: 30,
sex: 76
}
// 1: 创建类式组件
class MyComponent extends React.Component {
render() {
return (
<ul>
<li>姓名:{this.props.listData.name}</li>
<li>年龄:{this.props.listData.age}</li>
<li>性别:{this.props.listData.sex}</li>
</ul>
)
}
}
ReactDOM.render(
<MyComponent listData={listData1}/>,
document.getElementById('example')
);
ReactDOM.render(
<MyComponent listData={listData2}/>,
document.getElementById('example2')
);
</script>
</body>
</html>
2:props传参数类型的限制以及默认值
1)基础版
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>props类型限制</title>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script src="https://cdn.bootcss.com/prop-types/15.6.1/prop-types.js"></script>
</head>
<body>
<div id="example">
</div>
<div id="example2">
</div>
<script type="text/babel">
let listData1 = {
name: '张三',
age: 20,
sex: '男'
}
let listData2 = {
name: '李四',
age: 30,
sex: '女'
}
// 1: 创建类式组件
class MyComponent extends React.Component {
render() {
return (
<ul>
<li>姓名:{this.props.name}</li>
<li>年龄:{this.props.age}</li>
<li>性别:{this.props.sex}</li>
</ul>
)
}
}
// 标签必要性
MyComponent.propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
speak: PropTypes.func
};
// 执行默认值
MyComponent.defaultProps = {
age: 20,
};
ReactDOM.render(
<MyComponent {...listData1}/>,
document.getElementById('example')
);
ReactDOM.render(
<MyComponent {...listData2}/>,
document.getElementById('example2')
);
</script>
</body>
</html>
分析:
<script src="https://cdn.bootcss.com/prop-types/15.6.1/prop-types.js"></script>
- 需要使用
{...objData}进行展开,这是React提供的,这可不是扩展运算符哦,因为我们知道在js对象不能够使用扩展运算符,只有数组才有。本例中如果不使用{...listData2},那么限制条件和默认值就不起作用。 propTypes是React自定义组件提供的属性,而PropTypes是我们引入prop-types.js库才有的。
2)精简版
我们定义涉及到类自身的,最好都是包含在类中,而不是单独写出来,不好统一维护,于是就有如下精简的代码:
<script type="text/babel">
let listData1 = {
name: '张三',
age: 20,
sex: '男'
}
let listData2 = {
name: '李四',
age: 30,
sex: '女'
}
// 1: 创建类式组件
class MyComponent extends React.Component {
constructor(props) {
// super传不传参数,取决于是否在构造函数中通过this访问props
// 但在实际开发中不会这么用,我们可以直接使用传递过来的props,所以也证实了类一般很少写构造函数
// 第一种情况super(props)传参数
// super(props)
// 第二种情况super()不传递参数
super()
console.log('constructor中this.props==',this.props) // undefined
console.log('constructor中props==',props)
}
render() {
return (
<ul>
<li>姓名:{this.props.name}</li>
<li>年龄:{this.props.age}</li>
<li>性别:{this.props.sex}</li>
</ul>
)
}
// 标签必要性
static propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number
};
// 执行默认值
static defaultProps = {
age: 20,
};
}
ReactDOM.render(
<MyComponent {...listData1}/>,
document.getElementById('example')
);
ReactDOM.render(
<MyComponent {...listData2}/>,
document.getElementById('example2')
);
</script>
分析:
-
在类中使用赋值语句,我们是把属性添加到类的实例对象上,但
propTypes和defaultProps都需要挂在类自身身上,而加上static,可以把属性添加到类本身上,所以可以使用此方式给类添加属性。 -
是否使用构造函数
-
实际开发中很少使用。因为就算不使用,基本没什么影响。
-
如果我们使用了构造函数,
super是需要调用的。如果我们super()不传参数的话,在构造函数中this.props为undefined;如果我们super(props)传参数时,在构造函数中this.props就可以有值。但在实际开发中不会这么用,我们可以直接使用传递过来的props,所以也证实了类一般很少写构造函数
constructor(props) { // super传不传参数,取决于是否在构造函数中通过this访问props // 但在实际开发中不会这么用,我们可以直接使用传递过来的props,所以也证实了类一般很少写构造函数 // 第一种情况super(props)传参数 // super(props) console.log('constructor中this.props==',this.props) // 有值 // 第二种情况super()不传递参数 super() console.log('constructor中this.props==',this.props) // undefined console.log('constructor中props==',props) } -
-
函数式创建组件可以使用
props
<script type="text/babel">
let listData1 = {
name: '张三',
age: 20,
sex: '男'
}
let listData2 = {
name: '李四',
age: 30,
sex: '女'
}
// 1: 创建函数式组件
function MyComponent (props) {
console.log('MyComponent==',this) // undefined
return (
<ul>
<li>姓名:{props.name}</li>
<li>年龄:{props.age}</li>
<li>性别:{props.sex}</li>
</ul>
)
}
ReactDOM.render(
<MyComponent {...listData1}/>,
document.getElementById('example')
);
ReactDOM.render(
<MyComponent {...listData2}/>,
document.getElementById('example2')
);
</script>
因为函数式组件是没有组件的实例的,即this是undefined,所以state、refs在函数式组件中是不可以使用的,函数式组件之所以能够使用props,是因为函数可以接受参数。
props是只读属性,是不可以修改的
3:ref
1)string类型的ref
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<title>string类型的ref</title>
</head>
<body>
<div id="box"></div>
<script type="text/babel">
class MyComponent extends React.Component{
render() {
return (
<div>
<input ref="input1" type="text"/>
<button onClick={this.showData}>点击按钮</button>
</div>
)
}
showData = () => {
console.log('获取值',this.refs.input1.value)
}
}
ReactDOM.render(<MyComponent/>,document.getElementById('box'))
</script>
</body>
</html>
React官方不建议使用,因为查询数度慢
2)回调函数类型的ref
<script type="text/babel">
class MyComponent extends React.Component{
render() {
return (
<div>
<input ref={(currentNode) => {this.input1 = currentNode}} type="text"/>
<button onClick={this.showData}>点击按钮</button>
</div>
)
}
showData = () => {
// console.log('获取值1',this.input1.value)
}
}
ReactDOM.render(<MyComponent/>,document.getElementById('box'))
</script>
注意:
-
ref里面的回调函数不能是普通函数,普通函数this为undefined,应该是箭头函数。 -
内联函数的
ref调用几次函数呢?第一次初始化的时候调用一次。组件更新的时候调用了两次,第一次回调函数接收的是null,第二次才是节点内容
<script type="text/babel">
class MyComponent extends React.Component{
state = {isFlag:false}
render() {
return (
<div>
<h1>{this.state.isFlag ? '我是真值':'我是假值'}</h1>
<input ref={(currentNode) => {this.input1 = currentNode;console.log('@==',currentNode)}} type="text"/>
<button onClick={this.showData}>点击按钮</button>
<button onClick={this.changeData}>更新组件</button>
</div>
)
}
showData = () => {
console.log('获取值',this.input1.value)
}
changeData = () => {
let {isFlag} = this.state
this.setState({
isFlag: !isFlag
})
}
}
ReactDOM.render(<MyComponent/>,document.getElementById('box'))
</script>
分析:
只要state里面的状态值发生了改变,就会触发组件的更新,那么就会触发回调函数。即只有组件更新的时候,才会触发两次。
第一次初始化组件的时候:
组件更新的时候:
组件更新的时候,回调函数为什么回调两次?
这是React里面设置的,第一次起到清空的作用,第二次给新的函数,注意,是新的回调函数了。就是说初始化的时候,执行了一次回调函数。组件更新的时候,先把之前的回调函数清空,然后再执行新的回调函数。
解决的办法:
把回调函数定在组件实例的身上,这样每次都不会清空,而是从组件的实例上获取回调函数。
<script type="text/babel">
class MyComponent extends React.Component{
state = {isFlag:false}
render() {
return (
<div>
<h1>{this.state.isFlag ? '我是真值':'我是假值'}</h1>
<input ref={this.currentFn} type="text"/>
<button onClick={this.showData}>点击按钮</button>
<button onClick={this.changeData}>更新组件</button>
</div>
)
}
currentFn = (currentNopde) => {
console.log('@==',currentNopde)
this.input1 = currentNopde
}
showData = () => {
console.log('获取值',this.input1.value)
}
changeData = () => {
let {isFlag} = this.state
this.setState({
isFlag: !isFlag
})
}
}
ReactDOM.render(<MyComponent/>,document.getElementById('box'))
</script>
分析:
<input ref={this.currentFn} type="text"/>
--------------------------------------
currentFn = (currentNopde) => {
console.log('@==',currentNopde)
this.input1 = currentNopde
}
3)createRef类型的ref
<script type="text/babel">
class MyComponent extends React.Component{
state = {isFlag:false}
render() {
return (
<div>
<input ref={this.currentFn} type="text"/>
<button onClick={this.showData}>点击按钮</button>
</div>
)
}
currentFn = React.createRef()
showData = () => {
console.log('@=',this.currentFn.current.value)
}
}
ReactDOM.render(<MyComponent/>,document.getElementById('box'))
</script>