父子组件通信
-
props -父组件向子组件传递数据- 父组件
import Header from './Header' // 引入子组件Header this.state = { msg:'123' } run= () => { // ... } render() { return(<div><Header msg={this.state.msg} /><div>) } - 子组件
Header.jsconstructor(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 StoreContextReact渲染一个订阅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并回调; - 在组件间传递以回调的形式传递
refsclass 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里的执行机制。