父子组件通信
-
props -
父组件向子组件传递数据- 父组件
import Header from './Header' // 引入子组件Header this.state = { msg:'123' } run= () => { // ... } render() { return(<div><Header msg={this.state.msg} /><div>) }
- 子组件
Header.js
constructor(props) { // props 是一个对象,用于接收父组件的数据 super(props) } render() { return(<div> <span>{this.props.msg}<span> <button onClick={this.props.run}>Run Test</button> <div>) }
- 父组件
-
Context API -
跨层级通信,祖代组件向任意层级的后代组件传递
ps:Vue
中的provide/inject
来源于Context
Context
模式下有两个角色:Provider -
外层提供数据的组件,Consumer -
内存获取数据的组件。- 创建
Context
上下文,放在一个文件中,方便引用
当// storecontext.js import React from 'react' // Context 可以创建多个! const StoreContext = React.createContext(/*可以设置默认值*/) export default StoreContext
React
渲染一个订阅Context
对象的组件时,这个组件会从组件树中离自身最近的那个匹配的Provider
中读取到当前的context
值;只有当组件所在的树中没有匹配到Provider
时,设置的默认值才会生效。这有助于在不使用Provider
包装组件的情况下,对组件进行测试。 - 在父组件中使用
Provider
,它接收一个value
属性,当value
值发生变化时,它内部的所有消费组件都会重新渲染import StoreContext from './StoreContext' // 创建一个数据源 const store = { name: 'Tom', age: 20 } render() { return( <StoreContext.Provider value={store}> <AAA></AAA> </StoreContext.Provider> ) }
- 在后代组件中使用
Consumer
,它包裹一个函数,回调的参数正是距离自己最近的Provider
提供的数据源(value
);如果没有找到Provider
,则回调createContext()
的默认值;import StoreContext from './StoreContext' render() { return(<StoreContext.Consumer> { value => { return(<div> <p>姓名: {value.name}</p> <p>年龄: {value.age}</p> </div>) } } </StoreContext.Consumer>) } # 作为 props 直接把Consumer的值传递给组件 render() { return(<StoreContext.Consumer> { ctx => <TitleBar {...ctx} /> } </StoreContext.Consumer>) } // 函数式组件 function TitleBar(props) { console.log(props) return <div>TitleBar</div> }
-
Class.contextType
实现编程式消费最近Context
的值,可以在任何生命周期中访问它import StoreContext from './StoreContext' class Titlebar extends React.Component { componentDidMount() { // StoreContext 共享的值被赋予 this.context let value = this.context; } render() { let value = this.context; // ... } } Titlebar.contextType = StoreContext;
- 创建
-
refs -
父组件主动获取子组件的数据
React提供了refs
管理组件标签上的自定义属性ref
;<Header ref="header" /> this.refs.header // 访问子组件的数据
发布订阅,第三方包如
pubsub-js
-
defaultProps
与propTypes
- 父组件在调用子组件时,如果不给子组件传值,则子组件可以使用
defaultProps
定义默认值;class Header extends React.Component { ... } Header.defaultProps = { msg: '0' //如果父组件没有传递msg,则使用此默认值 } export default Header;
-
propTypes
:用于验证父组件传值类型的合法性;import PropTypes from 'prop-types' //react的一个内置模块 Header.propTypes = { msg: PropTypes.number //父组件传递msg的数据类型必须是number }
- 父组件在调用子组件时,如果不给子组件传值,则子组件可以使用
-
组件
state/props
的划分原则- 让组件尽可能少状态
对于只有UI渲染、数据展示、没有复杂交互的组件,应使用props
,而不是state
- 组件随着时间产生变化的数据,界面有交互,应使用
state
- 让组件尽可能少状态
ref
-
React v16.3
引入了React.createRef()
创建Ref
,字符串类型的Ref
将慢慢被抛弃;class MyComponent extends React.Component { constructor(props) { super(props); this.myRef = React.createRef(); } render() { return <div ref={this.myRef} />; } }
- React 会在组件加载时设置
ref
的current
属性,在卸载时则会改回null
; -
ref
的更新会发生在钩子componentDidMount()
或componentDidUpdate()
之前。
- React 会在组件加载时设置
- 访问
const node = this.myRef.current;
current
的值取决于节点的类型- 普通
HTML
元素,值为DOM对象 - React组件,值为实例对象
- 普通
- 函数式组件上不能使用
ref
属性,因为它没有实例,但它的内部可以使用ref
属性; -
React v16.3
推荐使用Ref
转发,从而把子组件的Ref
暴漏给父组件; - 回调式
Ref
这种设置ref
的方式可以更加细致地控制ref
的设置与解除。class CustomTextInput extends React.Component { constructor(props) { super(props); this.textInput = null; } this.setTextInputRef = element => { this.textInput = element; } render() { return <input type="text" ref={this.setTextInputRef} /> } }
-
ref
属性接收一个函数,在组件挂载时回调ref
函数,当卸载时传入null
并回调; - 在组件间传递以回调的形式传递
refs
class Parent extends React.Component { render() { return <CustomTextInput inputRef={el => this.inputElement = el} /> } } function CustomTextInput(props) { return <input ref={props.inputRef} /> }
-
生命周期
React V16.3
之前的生命周期
-
this.forceUpdate()
手动强制触发组件的render
渲染,会导致组件跳过shouldComponentUpdate()
,直接调用render()
;
应用场景:有些变量不在state
上,或者state
里的某个变量层次太深,更新时没有自动触发render()
。 -
React V16.0
引入新的生命周期:componentDidCatch()
,如果render()
函数抛出错误,该函数勾子可以捕捉到错误信息,且可以展示相应的错误提示。
注:它只是一个增量式的修改,完全不影响原有的生命周期勾子。
- 组件首次加载过程中触发的函数
constructor --> componentWillMount --> render --> componentDidMount
-
componentWillMount()
:组件将要挂载 -
render()
:数据/模板渲染 -
componentDidMount()
:组件加载完成 -
constructor()
和render()
在组件加载时会被触发,但并不属于生命周期函数
-
- 组件状态更新过程中触发的函数
shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate
-
shouldComponentUpdate()
:是否更新组件,常用于做组件优化,返回true
则更新,否则不更新。shouldComponentUpdate(nextProps, nextState) { // nextProps:表示父组件向当前组件传递的数据 // nextState:当前组件更新后的数据,只是还没有重新渲染DOM }
-
componentWillUpdate()
:将要更新组件; -
render()
:更新数据/模板; -
componentDidUpdate()
:数据更新完成。
-
- 在父组件中改变
props
传值时触发的函数:componentWillReceiveProps()
,然后才会触发组件更新的render()
函数。 - 组件销毁时触发的函数:
componentWillUnmount()
- 父组件
this.state = { flag:true } setFlag() { this.setState({ flag:!this.state.flag }) } render() { return(<div> { this.state.flag && <Header /> } <button onClick={this.setFlag}>挂载与销毁</button> </div>) }
- 子组件Header
componentWillUnmount() { console.log('Header被销毁') }
- 父组件
- 手动移除某个容器上的组件:
ReactDOM.unmountComponentAtNode(containerDom)
React V16.3
的新生命周期
React V16.3
的更新中,除了被热烈讨论的新Context API
之外,新引入的两个生命周期函数static getDerivedStateFromProps、getSnapshotBeforeUpdate
以及在未来V17.0
版本中即将被移除的三个生命周期函数componentWillMount、componentWillReceiveProps、componentWillUpdate
。这三个准备废弃的生命周期用getDerivedStateFromProps
替代。如果仍想使用废弃的生命周期,可以加上前缀:UNSAFE_
,如UNSAFE_componentWillMount
。
原来的生命周期在 React v16
推出 Fiber
之后就不合适了,因为如果开启 async rendering
,在 render()
之前的所有函数都可能被执行多次。
在 render()
之前执行的生命周期:componentWillMount、componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate
除了 shouldComponentUpdate
,其他三个都被静态函数getDerivedStateFromProps
取代。
这种做法强制开发者在 render()
执行之前只做无副作用的操作,而且能做的操作局限在根据 props
和 state
决定新的state
。
-
state getDerivedStateFromProps(props, state)
会在render()
之前被调用,并且在初始化挂载及后续更新时都会被调用,返回一个对象来更新state
,如果返回null
则不执行更新。 -
getSnapshotBeforeUpdate(prevProps, prevState)
在render()
之后、componentDidUpdate()
之前。使得组件能在发生更改之前从DOM中获取一些信息(如滚动位置)。返回值将作为参数snapshot
传递给componentDidUpdate(prevProps, prevState, snapshot)
错误边界
默认清空下,若一个组件在渲染期间(render
)发生错误,会导致整个组件树全部被卸载。
错误边界: 是一个组件,它能够捕获到渲染期间子组件发生的错误,并有能力阻止错误继续传播。
错误边界能捕获在渲染过程中 所有子组件的constructor
和生命周期函数内发生的错误。
错误边界不能捕获的错误类型:
- 发生在事件处理器里面的
- 异步代码,如
setTimeout、requestAnimationFrame
- 服务端渲染
- 自己本身抛出的错误
在代码层面上,只要一个类组件中定义了 static getDerivedStateFromError()
或者 componentDidCatch()
,它就是一个错误边界(组件)。
一般来说,getDerivedStateFromError()
是不允许发生副作用的,故而负责呈现一个备用的Fallback UI
给用户;componentDidCatch()
允许发生副作用,故负责打印错误日志,上报错误到远程服务器。
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
componentDidCatch(error, info) {
// You can also log the error to an error reporting service
// error 表示被抛出的错误
// info 表示一个含有 ```componentStack``` 属性的对象
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
然后可以把它当做一个普通的组件来用:
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
<ErrorBoundary>
使用起来就像try-catch
语句,只不过它是用于React
组件,且保留了React
原生的声明特性。
注意:
<ErrorBoundary>
不能捕获自己所产生的错误,只能捕获在它之下的组件树所产生的错误。
在<ErrorBoundary>
嵌套使用的情况下,如果某个<ErrorBoundary>
不能渲染一些错误信息(调用static getDerivedStateFromError()
失败),那么这个错误就会往上冒泡到层级最近的<ErrorBoundary>
。这也是try-catch
语句在Javascript
里的执行机制。