组件是独立且封闭的单元,默认情况下,只能使用组件自己的数据。在组件化过程中,我们将一个完整的功能拆分成多个组件,以更好的完成整个应用的功能。而在这个过程中,多个组件之间不可避免的要共享某些数据。为了实现这些功能,就需要打破组件的独立封闭性,让其与外界沟通。这个过程就是组件通讯。
组件是封闭的,要接收外部数据应该通过props来实现
props的作用:接收传递给组件的数据
传递数据:给组件标签添加属性
接收数据:函数组件通过参数props接收数据,类组件通过this.props接收数据
示例:
/* * props 函数组件 * */ const Hello = props => { console.log(props); return ( <div> props: {props.name} </div> ) } ReactDOM.render(<Hello name='xiaohao' age={18}/>, document.getElementById('root')) /* * props 类组件 * */ class Hello extends React.Component { render(){ console.log(this.props) return ( <div> props: {this.props.name} </div> ) } } ReactDOM.render(<Hello name='xiaohao' age={18}/>, document.getElementById('root'))
特点:
1、可以给组件传递任意类型的数据
示例:
class Hello extends React.Component { render(){ console.log(this.props) this.props.fn() return ( <div> props: {this.props.name} {this.props.zujian} </div> ) } } ReactDOM.render( <Hello name='xiaohao' age={18} list={[ 1, 2, 3 ]} fn = {() => console.log('这是一个函数')} zujian = {<p>这是一个p标签</p>} />, document.getElementById('root') )
2、props是只读的对象,只能读取属性的值,无法修改对象
3、注意:使用类组件时,如果写了构造函数,应该将props传递给super(),否则,无法在构造函数中获取到props
示例:
class Hello extends React.Component { constructor(props){ super(props) console.log(props); } render(){ console.log(this.props) this.props.fn() return ( <div> props: {this.props.name} {this.props.zujian} </div> ) } } ReactDOM.render( <Hello name='xiaohao' age={18} list={[ 1, 2, 3 ]} fn = {() => console.log('这是一个函数')} zujian = {<p>这是一个p标签</p>} />, document.getElementById('root') )
1、父组件 -> 子组件
1、父组件提供要传递的state数据
2、给子组件标签添加属性,值为state中的数据
3、子组件中通过props接收父组件中传递的数据
示例:
class Parent extends React.Component { state = { lastName: '顾' } render(){ return ( <div className="parent"> 父组件: <Child name={this.state.lastName}/> </div> ) } } const Child = props => { console.log(props); return ( <div className="child"> <p>子组件接收到父组件的数据:{props.name}</p> </div> ) } ReactDOM.render(<Parent/>, document.getElementById('root'))
2、子组件 -> 父组件
思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数
1、父组件提供一个回调函数(用于接收数据)
2、将该函数作为属性的值,传递给子组件
3、子组件通过props调用回调函数
4、将子组件的数据作为参数传递给回调函数
示例:
class Parent extends React.Component { state = { msg: '' } getChildMsg = data => { console.log('接收到儿子传来的消息', data); this.setState({ msg: data }) } render(){ return ( <div className="parent"> 父组件:{this.state.msg} <Child getMsg={this.getChildMsg}/> </div> ) } } class Child extends React.Component { state = { msg: '刷抖音' } handleClick = () => { // 子组件调用父组件中传过来的回调函数 this.props.getMsg(this.state.msg) } render(){ return ( <div className="child"> 子组件: <button onClick={this.handleClick}>点我</button> </div> ) } } ReactDOM.render(<Parent/>, document.getElementById('root'))
注意:回调函数中this指向问题!!!
3、兄弟组件
将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态
思想:状态提升
公共父组件职责:1、提供共享状态 2、提供操作共享状态的方法
要通讯的子组件只需通过props接收状态或操作状态的方法
示例:
class Content extends React.Component { state = { count: 0 } onIncrement = () => { this.setState({ count: this.state.count + 1 }) } render(){ return ( <div> <Child1 count={this.state.count}/> <Child2 onIncrement={this.onIncrement}/> </div> ) } } class Child1 extends React.Component { render(){ return ( <h1>计数器:{this.props.count}</h1> ) } } class Child2 extends React.Component { render(){ return ( <button onClick={this.props.onIncrement}>+1</button> ) } } ReactDOM.render(<Content/>, document.getElementById('root'))
更好的姿势:使用Context
作用:跨组件传递数据(比如:主题、语言等)
使用步骤:
1、调用React.createContext()创建Provider(提供数据)和Consumer(消费数据)两个组件。
2、使用Provider组件作为父节点。
3、设置value属性,表示要传递的数据。
4、调用Consumer组件接收数据。
示例:
const { Provider, Consumer } = React.createContext() class App extends React.Component { render (){ return ( <Provider value="小浩"> <div> 父组件 <Node/> </div> </Provider> ) } } const Node = props => { return ( <div className="node"> <SubNode/> </div> ) } const SubNode = props => { return ( <div className="subnode"> <Child/> </div> ) } const Child = props => { return ( <div className="child"> 我是子组件 <Consumer> {data => <span>我是子节点:{data}</span>} </Consumer> </div> ) } ReactDOM.render(<App />, document.getElementById('root'))
总结:
children属性
children属性:表示组件标签的子节点。当组件标签有子节点时,props就会有该属性。
children属性与普通的props一样,值可以是任意值(文本、React元素、组件,甚至是函数)。
示例:
// const App = props => { // console.log(props); // return ( // <div> // <h1>组件标签的子节点:</h1> // {props.children} // </div> // ) // } // ReactDOM.render(<App>我是app</App>, document.getElementById('root')) // const App = props => { // console.log(props); // return ( // <div> // <h1>组件标签的子节点:</h1> // {props.children} // </div> // ) // } // ReactDOM.render(<App><p>我是一个p标签</p></App>, document.getElementById('root')) const App = props => { console.log(props); props.children() return ( <div> <h1>组件标签的子节点:</h1> </div> ) } ReactDOM.render(<App>{() => console.log('我是一个函数')}</App>, document.getElementById('root'))
props校验
对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据。
如果传入的数据格式不对,可能会导致组件内部报错
关键问题:组件的使用者不知道明确的错误原因
props校验:允许在创建组件的时候,就指定props的类型、格式等
作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性
使用步骤:
1、安装包prop-types(yarn add prop-types / npm i props-types )
2、导入prop-types包
3、使用组件名:propTypes={}来给组件的props添加校验规则
4、校验规则通过PropTypes对象来指定
示例:
import PropTypes from 'prop-types' const App = props => { const arr = props.colors const lis = arr.map((item, index) => (<li key={index}>{item.name}</li>)) return <ul>{lis}</ul> } App.propTypes = { colors: PropTypes.array } ReactDOM.render(<App colors={[{name: 'niu'}]} />, document.getElementById('root'))
约束规则
1、常见类型:array、bool、func、number、object、string
2、React元素类型:element
3、必填项:isRequired
4、特定结构的对象:shape({})
示例:
import PropTypes from 'prop-types' const App = props => { return ( <div> <h1> props校验</h1> </div> ) } // 添加props校验 // 属性 a 的类型:数值(number) // 属性 fn 的类型:函数(func)并且为必填项 // 属性 tag 的类型: React元素(element) // 属性 filter 的类型: 对象({area: '上海', price: 1999}) App.propTypes = { a: PropTypes.number, fn: PropTypes.func.isRequired, tag: PropTypes.element, filter: PropTypes.shape({ area: PropTypes.string, price: PropTypes.number }) } ReactDOM.render(<App fn={() => console.log('校验函数')} />, document.getElementById('root'))
props的默认值
场景:分页组件 -> 每页显示条数
作用:给props设置默认值,在未传入props时生效
示例:
const App = props => { console.log(props); return ( <div> <h1> props默认值 {props.pageSize}</h1> </div> ) } // 添加props默认值 App.defaultProps = { pageSize: 10 } ReactDOM.render(<App />, document.getElementById('root'))
意义:组件的生命周期有助于理解组件的运行方式、完成更复杂的组件功能、分析组件错误原因等
组件的生命周期:组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程
生命周期的每个阶段总是伴随着一些方法调用,这些方法就是生命周期的钩子函数
钩子函数的作用:为开发人员在不同阶段操作组件提供了时机。
只有类组件才有生命周期
生命周期的三个阶段
1、每个阶段的执行时机
1、创建时(挂载阶段)
执行时机:组件创建时(页面加载时)
执行顺序:constructor() -> render() -> compoentDidMount()
示例:
class App extends React.Component { constructor(props){ super(props); console.warn('钩子函数 constructor'); this.state = { count: 0 } } render(){ console.warn('钩子函数 render'); // 错误演示 不要在render中调用setState方法 // this.setState({ // count: 1 // }) return ( <div id="title"> render </div> ) } componentDidMount(){ console.warn('钩子函数 componentDidMount'); const title = document.getElementById('title') console.log(title); } } ReactDOM.render(<App />, document.getElementById('root'))
2、更新时(更新阶段)
执行时机:1、setState() 2、forceUpdate() 3、组件接收到新的props
说明:以上三者任意一种变化,组件就会重新渲染
示例:
class App extends React.Component { constructor(props){ super(props); this.state = { count: 0 } } onIncrement = () => { // this.setState({ // count: this.state.count + 1 // }) // 强制更新 this.forceUpdate() } render(){ console.warn('钩子函数 render'); return ( <div> <Child count={this.state.count}/> <button onClick={this.onIncrement}>+1</button> </div> ) } } class Child extends React.Component { render(){ console.warn('子组件----钩子函数 render'); return ( <div> 计数:{this.props.count} </div> ) } } ReactDOM.render(<App />, document.getElementById('root'))
执行顺序:render() -> componentDidUpdate()
示例:
class App extends React.Component { constructor(props){ super(props); this.state = { count: 0 } } onIncrement = () => { this.setState({ count: this.state.count + 1 }) } render(){ console.warn('钩子函数 render'); return ( <div> <Child count={this.state.count}/> <button onClick={this.onIncrement}>+1</button> </div> ) } } class Child extends React.Component { render(){ console.warn('子组件----钩子函数 render'); return ( <div id='child'> 计数:{this.props.count} </div> ) } // 注意:如果要调用setState()更新状态,必须要放在一个if条件中 // 因为:如果直接调用setState()更新状态,也会导致递归 componentDidUpdate(prevProps){ console.log('更新前数据', prevProps, '更新后数据', this.props); console.warn('子组件----钩子函数 componentDidUpdate'); // const child = document.getElementById('child') // console.log(child.innerHTML); // 错误示范 // this.setState({}) // 正确示范 if(prevProps.count !== this.props.count){ // 发送ajax请求 this.setState({}) } } } ReactDOM.render(<App />, document.getElementById('root'))
3、卸载时(卸载阶段)
执行时机:组件从页面中消失
示例:
class App extends React.Component { constructor(props){ super(props); this.state = { count: 0 } } onIncrement = () => { this.setState({ count: this.state.count + 1 }) } render(){ console.warn('钩子函数 render'); return ( <div> {this.state.count >= 3 ? <p>到3la</p> : (<Child count={this.state.count}/>)} <button onClick={this.onIncrement}>+1</button> </div> ) } } class Child extends React.Component { componentDidMount(){ // 创建定时器 this.timeId = setInterval(()=>{ console.log('定时器正在运行'); }, 500) } render(){ console.warn('子组件----钩子函数 render'); return ( <div id='child'> 计数:{this.props.count} </div> ) } componentWillUnmount(){ console.warn('子组件----钩子函数 componentWillUnmount'); // 清洗计时器 clearInterval(this.timeId) } } ReactDOM.render(<App />, document.getElementById('root'))
2、每个阶段钩子函数的执行顺序
3、每个阶段钩子函数的作用
不常用钩子函数介绍
React组件复用概述
render props模式
思路分析:
使用步骤:
1、创建Mouse组件,在组件中提供复用的状态逻辑代码(1、状态 2、操作状态的方法)
2、将要复用的状态作为props.render(state)方法的参数,暴露到组件外部
3、使用props.render()的返回值作为要渲染的内容
示例:
class Mouse extends React.Component { // 鼠标位置 state = { x: 0, y: 0 } // 鼠标移动事件的事件处理程序 handleMouseMove = e => { this.setState({ x: e.clientX, y: e.clientY }) } // 监听鼠标移动事件 componentDidMount(){ window.addEventListener('mousemove', this.handleMouseMove) } render(){ // return null return this.props.render(this.state) } } class App extends React.Component { render(){ return ( <div> render props 模式 <Mouse render={(mouse) => (<p>鼠标的位置: x:{mouse.x} y:{mouse.y}</p>)} /> </div> ) } } ReactDOM.render(<App/>, document.getElementById('root'))
演示Mouse组件的复用
Mouse组件负责:封装复用的状态逻辑代码(1、状态 2、操作状态)
状态:鼠标坐标(x, y)
操作状态的方法:鼠标移动事件
传入的render prop负责:使用复用的状态来渲染UI结构
示例:
import img from "./images/logo192.png" class Mouse extends React.Component { // 鼠标位置 state = { x: 0, y: 0 } // 鼠标移动事件的事件处理程序 handleMouseMove = e => { this.setState({ x: e.clientX, y: e.clientY }) } // 监听鼠标移动事件 componentDidMount(){ window.addEventListener('mousemove', this.handleMouseMove) } render(){ // return null return this.props.render(this.state) } } class App extends React.Component { render(){ return ( <div> render props 模式 <Mouse render={mouse => (<p>鼠标的位置: x:{mouse.x} y:{mouse.y}</p>)} /> <Mouse render={mouse => (<img src={img} alt="logo" style={{ position: 'absolute', top: mouse.y - 96, left: mouse.x - 96 }}/>)} /> </div> ) } } ReactDOM.render(<App/>, document.getElementById('root'))