上一篇:重学 React 之基础认知
本文 React 用例版本:v17.0.2
React 三大属性是指:
State:来自组件内部的状态Props:来自外部的属性Refs:表示组件内的某一个元素
State
state 是一个对象,它包含组件的数据状态,当状态变化时,会触发视图的更新。你可以理解它的作用跟 Vue 中的 data 对象类似。
定义和使用 state
class MyComponent extends React.Component{
constructor(){
super()
this.state = {msg:'hello'} // 定义 state
}
render(){
return (
<div>
<h1>{this.state.msg}</h1>
</div>
)
}
}
上面代码中,在构造函数中定义了 state 对象,然后在组件中可以使用 this 来获取这个对象及其中的数据。
注意不要跟 Vue 中的使用方法弄混了,在 Vue 的模板里使用 data 数据时是不需要 this 调用的。
state 也可以直接定义在类中,如下:
class MyComponent extends React.Component{
state = { msg:'hello' }
render(){
return (
<div>
<h1>{this.state.msg}</h1>
</div>
)
}
}
修改 state
可以通过事件来修改 state 中的数据状态。React 中定义事件主要是在原生事件的基础上把命名改成驼峰命名,比如原生的点击事件 onclick 在 React 中就要写成 onClick。
修改 state 例子:
class MyComponent extends React.Component{
state = { msg:"hello" }
render(){
return (
<div>
<p>{this.state.msg}</p>
<button onClick={this.changeMsg.bind(this)}>改变</button>
</div>
)
}
changeMsg (){
console.log(this.state);
this.setState({ msg: "你好" });
}
}
通过定义事件 changeMsg 来修改了 state 中的 msg 数据,可以注意到使用事件时使用 bind 进行了绑定操作,这里解释下原因:
changeMsg 方法是挂载在类的原型上的,如果类的实例直接调用此方法,那么 this 就是指向这个实例,但是触发点击事件时,并不是这个类的实例调用的这个方法,是全局调用的。如果是全局调用的,changeMsg 中的 this 应该指向的是 window,但是这里指向的是 undefined,其主要原因是因为类中定义的方法默认开启了“严格模式”,导致 this 指向了 undefined 了。
我们需要在 changeMsg 方法中获取组件的 state,所以需要主动改变 this 指向到这个类上,所以需要用 bind 绑定 this。
修改 state 中的数据,需要使用 setState 内置方法,这样修改后的数据才能触发视图的更新。setState 是一种合并更新。
事件的 bind 绑定也可以在类组件构造函数里进行,如下:
class MyComponent extends React.Component {
// 将 state 和 事件绑定移入构造函数中时,一定要先调用 super 方法
constructor() {
super();
this.state = { msg: "hello" };
this.changeMsg = this.changeMsg.bind(this); // 在这里提前绑定了this,jsx中使用就不需要再绑定了
}
render() {
return (
<div>
<p>{this.state.msg}</p>
<button onClick={this.changeMsg}>改变</button>
</div>
);
}
changeMsg() {
console.log(this.state);
this.setState({ msg: "你好" });
}
}
关于 setState
这里多说一句 setState。
setState 修改状态是一种异步操作,上例中使用的是对象式的使用方法,即第一个参数传入的是一个对象,此时它还可以传入第二个参数,是一个函数,该回调函数是在状态更新,且视图更新完成后调用,可以在这个函数中拿到最新的状态。
setState 还有函数式调用方法,即第一个参数传入一个函数,它的返回值是一个修改状态值的对象。该函数有两个参数,分别是当前组件的 state 和 props。此时 setState 也有一个回调函数,在状态更新,且视图更新完成后调用。
另外 setState 也不一定是同步或者异步的,是根据当前情况来决定的。立个flag,这个后面有时间再详细补充说明。
函数组件无法使用 state
函数组件内部因为没有 this,所以无法使用 state。
Props
React 中组件通过 props 属性接收外部传入的数据,这点 Vue 跟 React 是一致的。例子:
<script type="text/babel">
class MyComponent extends React.Component {
render() {
return (
<ul>
<li>{this.props.name}</li>
<li>{this.props.age}</li>
</ul>
);
}
}
ReactDOM.render(
<MyComponent name="Bob" age="18" />,
document.getElementById("test")
);
</script>
注意要使用 this 调用 props 属性哦。
如果需要在构造函数中使用 props,必须接收 props 参数,并且传入 super 方法中,像这样:
// ...
constructor(props) {
super(props); // 这里必须要将接收的 props 作参数传入
console.log(this.props);
}
// ...
Props 类型检查
如果组件需要传递一个变量或者数字类型,需要加上 {},像这样:
<MyComponent name="Bob" age={18} />
同等情况下的 Vue 中,需要加冒号 : 绑定属性。
有时候我们需要对传入组件的数据进行类型校验,以保证程序的正常运行,此时可以使用一个校验库 prop-types。
自 React v15.5 起,
React.PropTypes已移入 prop-types
如果通过 script 标签引入此库,会在 window 上挂载一个 PropTypes (注意首字母大写)变量,此时就可以用这个来校验 props 属性类型,例子:
class MyComponent extends React.Component {
render() {
return (
<ul>
<li>{this.props.name}</li>
<li>{this.props.age}</li>
</ul>
);
}
}
// 校验类型
MyComponent.propTypes = {
name: PropTypes.string, // 这里的 PropTypes 变量是全局挂载的
age: PropTypes.number,
};
ReactDOM.render(
<MyComponent name="Bob" age={18} />,
document.getElementById("test")
);
例子中是在组件对象上以 . 形式直接添加属性 propTypes 的。要想校验 props 属性类型,需要在组件类上设置 propTypes 属性(注意首字母小写),而验证器则是 PropTypes 提供。可参考文档:使用 PropTypes 进行类型检查。
你也可以在把 propTypes 属性作为组件类的静态属性写入:
<script type="text/babel">
class MyComponent extends React.Component {
render() {
return (
<ul>
<li>{this.props.name}</li>
<li>{this.props.age}</li>
</ul>
);
}
// propTypes 作为类的静态属性
static propTypes = {
name: PropTypes.string,
age: PropTypes.number,
};
}
ReactDOM.render(
<MyComponent name="Bob" age={18} />,
document.getElementById("test")
);
</script>
函数组件使用 props
函数组件无法使用 state,但是可以使用 props,因为 props 可以通过参数传入函数中。例子:
<script type="text/babel">
// 函数组件
function MyComponent(props) {
return (
<ul>
<li>{props.name}</li>
<li>{props.age}</li>
</ul>
);
}
// 校验类型
MyComponent.propTypes = {
name: PropTypes.string,
age: PropTypes.number,
};
ReactDOM.render(
<MyComponent name="Bob" age={18} />,
document.getElementById("test")
);
</script>
批量传递 props
props 可以批量传递:
<script type="text/babel">
class MyComponent extends React.Component {
render() {
return (
<ul>
<li>{this.props.name}</li>
<li>{this.props.age}</li>
</ul>
);
}
}
const data = { name: "Bob", age: 18 };
// 使用 {...data} 的方式可以批量传递属性
ReactDOM.render(
<MyComponent {...data} />,
document.getElementById("test")
);
</script>
Refs
React 中的 Refs 可以让我们访问 DOM 节点,它有三种使用方式:
旧的方式,String 类型的 Refs
此种方式已不被推荐使用,但是还能用,在以后的版本中可能会移除,使用比较简单:
<script type="text/babel">
class MyComponent extends React.Component {
handleAlert = () => {
// 在 refs 中获取定义的 ref 标识
const { myInput } = this.refs;
console.log(myInput); // <input type="text">
alert(myInput.value);
};
render() {
return (
<div>
{/* 使用 ref="" 方式直接定义字符串标识 */}
<input ref="myInput" type="text" />
<button onClick={this.handleAlert}>alert</button>
</div>
);
}
}
ReactDOM.render(<MyComponent />, document.getElementById("test"));
</script>
回调 Refs
此种方式定义的 ref 不再是字符串,而是一个回调函数,函数的参数就是当前标识的 DOM 节点或者组件实例。
<script type="text/babel">
class MyComponent extends React.Component {
handleAlert = () => {
// 直接从组件实例上获取 myInput
console.log(this.myInput); // <input type="text">
alert(this.myInput.value);
};
render() {
return (
<div>
{/* ref 直接定义成一个回调函数,参数就是节点本身,将它赋值给组件的一个 myInput 属性 */}
<input ref={(ele) => (this.myInput = ele)} type="text" />
<button onClick={this.handleAlert}>alert</button>
</div>
);
}
}
ReactDOM.render(<MyComponent />, document.getElementById("test"));
</script>
如上代码演示了使用回调 Refs 的方法,但是这种方式有一个需要注意的地方,React 官网给出以下提示:
如果
ref回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
意思是说,像上面这种定义内联函数的方式,在组件更新时,即除第一次外,后面 render 函数再执行时,这个定义的 ref 内联函数会被调用两次,其参数第一次是 null,第二次是 DOM 元素。之所以出现这样的情况,是因为组件更新时,会清空一下之前的 ref,保证更新。
其实这种情况是无关紧要的,我们可以照常使用内联函数的方式定义 ref,如果想避免这种情况,可以把内联函数移出来写:
<script type="text/babel">
class MyComponent extends React.Component {
// 内联函数移出来写
setInputRef = (ele) => {
this.myInput = ele;
console.log(ele);
};
handleAlert = () => {
console.log(this.myInput);
alert(this.myInput.value);
};
render() {
return (
<div>
{/* 这里不再直接定义内联函数 */}
<input ref={this.setInputRef} type="text" />
<button onClick={this.handleAlert}>alert</button>
</div>
);
}
}
ReactDOM.render(<MyComponent />, document.getElementById("test"));
</script>
新的,被推荐的创建 Refs 方法
现在 React 推荐使用 React.createRef(),然后通过 ref 属性附加到 React 元素上。
<script type="text/babel">
class MyComponent extends React.Component {
// 创建 ref
myInput = React.createRef();
handleAlert = () => {
console.log(this.myInput.current); // 这里需要注意,元素是在 current 属性上
alert(this.myInput.current.value);
};
render() {
return (
<div>
{/* 将创建好的 ref 附加到元素上 */}
<input ref={this.myInput} type="text" />
<button onClick={this.handleAlert}>alert</button>
</div>
);
}
}
ReactDOM.render(<MyComponent />, document.getElementById("test"));
</script>
上面就是使用 React.createRef() 方法创建 ref 的方式,特别需要注意的是,创建出来的 ref 的值是一个对象,我们需要的 DOM 元素是放在对象的 current 属性上,如上面的 this.myInput.current。
结尾
如有错误,欢迎指出。