目录
- 组件组件实例的三大核心属性-State
- State 不可以直接修改
- State 的更新是合并
- State 的更新可能是异步的
- 组件实例对象的三大核心属性-Props
- props.children
- 使用defaultProps设置默认的prop值
- 使用propTypes进行类型检查
- 组件实例对象的三大核心属性-Refs
- 字符串形式的Ref
- 回调函数形式的Ref
- createRef
- 访问Refs
- Refs 转发
组件组件实例的三大核心属性-State
状态 state 是组件实例对象最重要的属性之一,它的值是一个对象,可以包含多个 key-value 的组合。
当组件中的一些数据在某些时刻发生变化时,就需要使用 state 来跟踪状态。state 是私有的,并且完全受控于当前组件,除了拥有并设置了它的组件,其他组件都无法访问。
组件被称为状态机,通过更新组件的 state 来重新渲染组件,更新对应的页面显示。
this.props 和 this.state 是 React 本身设置的,且都拥有特殊的含义,但是其实可以向类组件中随意添加不参与数据流的额外字段(比如 this.timerID)。
state 和 props 之间最重要的区别是:
- props 由父组件传入,而 state 由组件本身管理。
- 组件不能修改 props,但可以修改 state。
class Weather extends React.Component{
constructor(props) {
super(props)
// 初始化 state
this.state = {
isHot: false,
}
}
// state可以简写成如下形式
// 原因是:类中可以直接写赋值语句,实际上就是直接给实例对象上添加属性
state = {
isHot: false,
}
componentDidMount() {
// 更改 state
this.setState({
isHot: !this.state.isHot,
})
}
...
}
State 不可以直接修改
初始化 state 之后,在其他地方不可以直接修改 state,而是应该使用 React 内置的一个 API:setState() 来修改。
constructor() 只初始化的时候调用一次。
render() 会调用 1+n 次,1是初始化,n 是状态更新的次数(也就是说,每次 setState() 之后, React 都会调用一次 render())。
// Wrong,此代码不会重新渲染组件
this.state.comment = 'Hello';
// Correct
this.setState({comment: 'Hello'});
setState() 有两种写法:
setState(nextState, [callback]):对象式的 setState。
参数:
- nextState:将要设置的新状态,该状态会和当前的 state 合并。
- callback:可选参数,回调函数。该函数会在状态更新完毕,且界面也更新后(render() 后)调用。
this.setState({
count: this.state.count +,
}, () => {
console.log(this.state.count)
})
setState(updater, [callback]):函数式的 setState。
参数:
- updater:是一个函数,可以接收到 state 和 props 作为参数,返回值为将要设置的新状态。
- callback:可选参数,回调函数。该函数会在状态更新完毕,且界面也更新后(render() 后)调用。
this.setState((state, props) => ({
count: state.count +,
}), () => {
console.log(this.state.count)
})
对象式的 setState 是函数式的 setState 的简写方式(语法糖)。这两种写法的使用原则:
如果新状态不依赖于原状态,使用对象方式;如果新状态依赖于原状态,使用函数方式。如果需要在 setState() 执行后获取最新的状态数据,要在第二个参数 callback 函数中读取。
State 的更新是合并
setState() 的更新是合并,不是替换。
constructor(props) {
super(props);
// state 包含几个独立的变量
this.state = {
isHot: false,
wind: '微风',
}
}
componentDidMount() {
// 此处调用 setState() 更新了 isHot 的值,但是 wind 的值也并没有丢失,所以说明更新的这个动作是合并
this.setState({
isHot: !this.state.isHot,
})
}
State 的更新可能是异步的
出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。
因为 this.props 和 this.state 可能会异步更新,所以不要依赖他们的值来更新下一个状态。要解决这个问题,可以让 setState() 接收一个函数而不是一个对象。
// Wrong
this.setState({
count: this.state.count +,
})
console.log(this.state.counter) //此时直接读取获取到的仍然是旧的 count 值
// Correct
this.setState({
count: this.state.count +,
}, () => {
console.log(this.state.counter) //此时读取获取到的是新的 count 值
})
组件实例对象的三大核心属性-Props
当 React 元素为用户的自定义组件时,它会将所接收的标签属性及子组件转换为单个对象传递给组件,这个对象被称之为 “props”。
props 是 React 组件的输入。它们是组件外部向组件内部传递变化的数据。
props 是只读的,组件无论是使用函数组件还是类组件,都决不能修改自身的 props。
// 类组件
class Person extends React.Component{
render(){
const {name, age, sex} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age+}</li>
</ul>
)
}
}
// 函数式组件
function Person(props){
const {name, age, sex} = props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age+}</li>
</ul>
)
}
ReactDOM.render(<Person name="jerry" age={} sex="男"/>, document.getElementById('test'))
// 批量传递 props 的简写方法(或者叫批量传递标签属性):
// 原生 JS 中扩展运算符是不能展开对象的。
// 由于 React 和 Babel 的原因,扩展运算符可以展开对象,但仅仅适用于标签属性的传递,别的地方不支持。
ReactDOM.render(<Person {...{
name: 'jerry',
age:,
sex: '男'
}} />, document.getElementById('test'))
props.children
每个组件都可以获取到 props.children,它包含组件的开始标签和结束标签之间的内容。
<Welcome>Hello world!</Welcome>
// 不写标签体,写成 children 标签属性也可以
<Welcome children='Hello world!'></Welcome>
// 在 Welcome 组件中获取 props.children,就可以得到字符串 Hello world!
function Welcome(props) {
return <p>{props.children}</p>;
}
使用defaultProps设置默认的prop值
可以通过配置特定的 defaultProps 属性来定义 props 的默认值。
// 给组件加上 defaultProps 的属性
Person.defaultProps = {
title: '我是详情'
}
// 简写:简写的这种方式只适用于类组件,因为函数式组件中是没有 static 的
class Person extends React.Component{
static defaultProps = {
title: '我是详情'
}
}
使用propTypes进行类型检查
PropTypes 提供一系列验证器,可用于确保组件接收到的数据类型是有效的。当传入的 prop 值类型不正确时,JavaScript 控制台将会显示警告。
propTypes 类型检查发生在 defaultProps 赋值后,所以类型检查也适用于 defaultProps。
出于性能方面的考虑,propTypes 仅在开发模式下进行检查。
// 只要给组件加上 propTypes 属性,React 就会认为是在加规则
Person.propTypes = {
// React.PropTypes 是 React 内置的属性
title: React.PropTypes.string.isRequired, // 错误
// 自 React v.5 起,React.PropTypes 已移入另一个包中,需要的话要使用`import PropTypes from 'prop-types'`引入,引入之后全局就会有了一个对象 PropTypes
title: PropTypes.string.isRequired, // 正确
speak: PropTypes.func,
}
/// 简写:简写的这种方式只适用于类组件,因为函数式组件中是没有 static 的
class Person extends React.Component{
static propTypes = {
title: PropTypes.string.isRequired,
}
}
组件实例对象的三大核心属性-Refs
PS:勿过度使用 Refs
组件内的标签可以定义 ref 属性来标识自己,都会被收集到组件实例对象的 refs 属性下,这样,通过 this.refs.ref属性 就可以访问到 ref 当前所处的真实节点。
无法在函数式组件上使用 ref 属性。
Ant Design 中很多组件都获取不到 ref,可以包裹或内嵌一层自己创建的元素以获取 ref。
字符串形式的Ref
React 不推荐使用字符串形式的 ref,它已过时并可能会在未来的版本中被移除,这种方式存在一些效率上的问题。
class Demo extends React.Component {
showData = () => {
console.log(this) // 打印可以看到组件的实例对象上 this 有 refs 属性,属性值是 key-value 的对象 ,其中有一个key 就是 input,value 是 ref 当前所处的真实节点。
// 访问 refs
alert(this.res.input.value)
}
render() {
return (
<div>
// 创建、绑定 refs
<input ref="input" />
<button onClick={this.showData}>点击</button>
</div>
)
}
}
回调函数形式的Ref
如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,第二次才会传入 DOM 元素。这是因为在每次 render 渲染时都会创建一个新的函数实例,所以 React 会首先清空旧的 ref,然后才会设置新的。这个问题大多数情况下是无关紧要的。
初次渲染时不会,因为初次渲染时没有旧的 ref 需要去清空。
class Demo extends React.Component {
showData = () => {
// 访问 refs
alert(this.input.value)
}
render() {
return (
<div>
// 创建、绑定 refs
// render 方法执行的时候会自动调用 ref 的回调函数,并且会把当前所处的真实节点作为参数传递进去,然后将这个节点赋值给组件实例自身的一个自定义属性上
<input ref={c => this.input = c} />
<button onClick={this.showData}>点击</button>
</div>
)
}
}
通过将 ref 的回调函数定义成类的绑定函数的方式可以避免上述问题。
class Demo extends React.Component {
setInputRef = (c) => {
// 绑定 refs
this.input = c
}
showData = () => {
// 访问 refs
alert(this.input.value)
}
render() {
return (
<div>
// 创建 refs
// 更新时也不会重复触发 setInputRef,因为它已经放在实例自身了
<input ref={this.setInputRef} />
<button onClick={this.showData}>点击</button>
</div>
)
}
}
createRef
React.createRef() 是 React 内置的一个 API,调用后可以返回一个容器,该容器存储被 ref 所标识的节点。该容器是专人专用的。
class Demo extends React.Component {
// 创建 refs
myRef = React.createRef()
showData = () => {
// 访问 refs
alert(this.myRef.current.value)
}
render() {
return (
<div>
// 绑定 refs
// 下面一行代码在执行的时候,React 发现了 ref 属性,并且发现属性值是用 createRef 创建出来的一个容器,这时, React 会把当前 ref 所在的那个节点直接存储到那个容器里面
<input ref={this.myRef} />
<button onClick={this.showData}>点击</button>
</div>
)
}
}
访问Refs
当 ref 被传递给 render 中的元素时,对该节点的引用可以在 ref 的 current 属性中被访问。
ref 的值根据节点
Refs 转发
Refs 转发是一个可选特性,其允许某些组件接收 ref,并将其向下传递给子组件。
ref 转发不仅限于 DOM 组件,也可以转发 refs 到 class 组件实例。
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
FancyButton 使用 React.forwardRef 来获取传递给它的 ref,然后转发到它渲染的 DOM button。这样,使用 FancyButton 的组件可以获取底层 DOM 节点 button 的 ref ,并在必要时访问,就像其直接使用 DOM button 一样。
上述代码的执行步骤如下:
- 通过调用 React.createRef 创建了一个 React ref 并将其赋值给 ref 变量;
- 通过指定 ref 为 JSX 属性,将其向下传递给 <FancyButton ref={ref}>;
- React 传递 ref 给 forwardRef 内函数 (props, ref) => ...,作为其第二个参数;
- 向下转发该 ref 参数到 <button ref={ref}>,将其指定为 JSX 属性;
- 当 ref 挂载完成,ref.current 将指向 <button> DOM 节点;