1 父子通信
-
在父组件中的子组件标签上将父组件的变量,方法通过属性的形式让子组件可以访问
-
在子组件中通过this.props.属性名访问对应变量或方法
-
方法应定义为箭头函数,避免不同组件调用方法导致this指向混乱
// 以todos功能为例 // 父组件 import React, { Component } from 'react' import AddItem from './AddItem' import Todolist from './Todolist' class Content extends Component { constructor(props) { super(props) this.state = {//父组件中的变量 value: '', arrList: [] } } // 定义方法 // 用setState函数修改state中变量,避免直接对state中的变量进行操作 // 为了子组件调用父组件方法时不改变this指向,应将方法定义为箭头函数 handleChange = (value) => { this.setState({//异步操作,但若在定时器中使用就是同步操作 value }) } handleClick = () => { this.setState({ arrList: [...this.state.arrList, this.state.value], value: '' }) } deleteClick = (id) => { let temparr = this.state.arrList temparr.splice(id,1) this.setState({ arrList: temparr }) } render() { return ( <div> <AddItem value = {this.state.value} handleChange = {this.handleChange} handleClick = {this.handleClick} /> <Todolist arrList = {this.state.arrList} deleteClick = {this.deleteClick} /> </div> ) } } export default Content // 子组件 AddItem import React, { Component, Fragment } from 'react' class AddItem extends Component { getInpValue = (e) => { this.props.handleChange(e.target.value) } render() { return ( <Fragment> <input type = "text" value = {this.props.value} onChange = {this.getInpValue} /> <button onClick = {this.props.handleClick}>添加任务</button> </Fragment> ) } } export default AddItem // 子组件 Todolist import React, { Component } from 'react' class Todolist extends Component { // 使用父组件方法需要传参,应在子组件方法中调用,通过bind绑定参数给子组件方法传参 // 虽然父组件定义方法时箭头函数时,子组件给该方法bind绑定参数也可以,但以上方法更可靠 deleteItem (i) { this.props.deleteClick(i) } render() { return ( <ul> { this.props.arrList.map((item,index) => { return ( <li key = {index}> {item} <span onClick = {this.props.deleteClick.bind(this,index)}>X</span> </li> ) }) } </ul> ) } } export default Todolist
2 利用context跨组件通信
-
context的功能
- 在react没有类似vue中的事件总线来解决这个问题
- 我们只能借助它们共同的父级组件来实现,将非父子关系装换成多维度的父子关系
- react提供了context api来实现跨组件通信, React 16.3之后的contextapi较之前的好用
-
使用方法
-
React.createContext() 我们需要用到createContext创建上下文
-
返回的是一个对象 对象内有2个组件
- Provider:设置共享状态及方法
- 通过value去传递
- Consumer:接收共享状态或方法
- 只要被 Provider这个组件包裹的所有组件都可以通过Consumer接收
- Consumer 组件 内部必须返回一个函数,这个函数会接受到 Provider传递过来的状态及方法
- Provider:设置共享状态及方法
-
示例:按钮实现加/减
- index.js
// index.js import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import CountProvider from './context' // 组件标签内就算有内容也不会自动去解析,需在组件内部通过this.props.children获取使用 ReactDOM.render( ( <CountProvider> <App /> </CountProvider> ), document.getElementById('root'));
-
context.js
import React, { createContext, Component, Fragment } from 'react' // Fragment不会生成DOM标签,一般用来做根标签,也可以用<></>代替 const {Consumer, Provider} = createContext() class CountProvider extends Component { constructor(props) { super(props) this.state = { num: 10 } } addNum = () => { this.setState({ num: this.state.num + 1 }) } minusNum = () => { this.setState({ num: this.state.num - 1 }) } // 此处this.props.children获取到的是APP组件,故在App组件中使用的组件都可以可以通过Consumer接收 render() { return ( <Fragment> <Provider value = {{ num: this.state.num, addNum: this.addNum, minusNum: this.minusNum }}> {this.props.children} </Provider> </Fragment> ) } } // export 导出语法有 // export let/const 变量名 // 或export function 函数名 () {} // 或export {变量名} // export default 可直接导出 变量,但只能使用一次 export default CountProvider export {Consumer, Provider}
-
App.js
import React, { Component } from 'react' import CountButton from './components/button' import Count from './components/count' class App extends Component { render () { return ( <div> <CountButton type='add'>+</CountButton> <Count /> <CountButton type='minus'>-</CountButton> </div> ) } } export default App;
-
/count/index.js
import React, { Component } from "react"; import {Consumer} from '../../context' class Count extends Component { render() { return ( <Consumer> { ({num}) => { return ( <span>{num}</span> ) } } </Consumer> ) } } export default Count
-
/button/index.js
import React, { Component } from 'react' import { Consumer } from '../../context' class CountButton extends Component { render() { return ( <Consumer> { ({addNum, minusNum})=>{ const setFunc = this.props.type === 'add' ? addNum : minusNum return ( <button onClick={setFunc}> {this.props.children} </button> ) } } </Consumer> ) } } export default CountButton
-
3 ref通信
-
适用场景
- 对DOM 元素焦点的控制、内容选择或者媒体播放
- 通过对DOM元素控制,触发动画特效
- 集成第三方 DOM 库
-
避免使用
- 避免使用 refs 去做任何可以通过声明式实现来完成的事情
- 例如,避免在Dialog、Loading、Alert等组件内部暴露 open(), show(), hide(),close()等方法,最好通过 isXX属性的方式来控制
-
使用限制
-
函数组件内部不支持使用 字符串 refs (支持 createRef 、 useRef 、 回调 Ref)
-
不能在函数组件上使用
ref
属性,因为函数组件没有实例 -
useRef 仅限于在函数组件内使用
-
3.1 String类型的refs
-
在元素或组件标签上设置ref属性
<button ref = 'btnRef' onClick = {this.handleClick}>dianwo</button> <Son ref = 'sonRef' />
-
通过this.refs得到所有设置了ref属性的元素或组件实例
- 如通过this.refs.sonRef可访问Son组件中的状态和方法
- 通过this.refs.btnRef操作真实button DOM
-
过时的API,官方建议使用 回调函数 或者 createRef API的方式来替换
-
函数组件内部不支持使用
3.2 createRef API
-
在React16.3 版本中引入的
-
使用
React.createRef()
创建Refs
,并通过ref
属性附加至React
元素上。通常在构造函数中,将Refs
分配给实例属性,以便在整个组件中引用。 -
当
ref
被传递给render
中的元素时,对该节点的引用可以在ref
的current
属性中访问 -
ref
的值根据节点的类型而有所不同 -
当
ref
属性用于HTML
元素时,构造函数中使用React.createRef()
创建的ref
接收底层DOM
元素作为其current
属性 -
当
ref
属性用于自定义的 class 组件时,ref
对象接收组件的挂载实例作为其current
属性 -
不能在函数组件上使用
ref
属性,因为函数组件没有实例
this.sonRef = createRef()
<Son ref = {this.sonRef} />
this.sonRef.current
3.3 useRef API
-
useRef
是 React16.8 中引入的,只能在函数组件中使用 -
使用
React.useRef()
创建Refs
,并通过ref
属性附加至React
元素上 -
返回的 ref 对象在组件的整个生命周期内保持不变
-
当
ref
被传递给 React 元素时,对该节点的引用可以在ref
的current
属性中访问
import React from 'react';
export default function MyInput(props) {
const inputRef = React.useRef(null);
React.useEffect(() => {
inputRef.current.focus();
});
return (
<input type="text" ref={inputRef} />
)
}
3.4 回调形式的refs
-
在react较早的版本中,我们推荐使用 回调形式的refs
-
这种方式可以帮助我们更精细的控制何时 Refs 被设置和解除
-
将回调函数传递给
React元素
的ref
属性。这个函数接受 React 组件实例 或 HTML DOM 元素作为参数,将其挂载到实例属性上 -
React 会在组件挂载时,调用
ref
回调函数并传入 DOM元素(或React实例),当卸载时调用它并传入null
。在componentDidMount
或componentDidUpdate
触发前,React 会保证 Refs 一定是最新的
import React from 'react';
export default class MyInput extends React.Component {
constructor(props) {
super(props);
this.inputRef = null;
this.setTextInputRef = (ele) => {
this.inputRef = ele;
}
}
componentDidMount() {
this.inputRef && this.inputRef.focus();
}
render() {
return (
<input type="text" ref={this.setTextInputRef}/>
)
}
}
参考: 你想知道的关于 Refs 的知识都在这了 ---刘小夕
基础决定未来,一步一个脚印