Skip to content

React 组件与通信 ​

组件通信方式 ​

方式适用场景
props父传子
props 回调子传父
ref父调子方法
Context跨层级
children插槽式
状态管理(Redux)全局

父传子(props) ​

jsx
// 父组件
<Child title="标题" count={this.state.count} />

// 函数组件接收
function Child(props) {
  return <h1>{props.title}</h1>
}

// 类组件接收
class Child extends Component {
  render() {
    return <h1>{this.props.title}</h1>
  }
}
// 父组件
<Child title="标题" count={this.state.count} />

// 函数组件接收
function Child(props) {
  return <h1>{props.title}</h1>
}

// 类组件接收
class Child extends Component {
  render() {
    return <h1>{this.props.title}</h1>
  }
}

props 类型验证 ​

jsx
import PropTypes from 'prop-types'

class MyComponent extends Component {
  static propTypes = {
    title: PropTypes.string.isRequired,
    count: PropTypes.number,
    user: PropTypes.object,
    onClick: PropTypes.func,
    children: PropTypes.element
  }

  static defaultProps = {
    count: 0
  }
}
import PropTypes from 'prop-types'

class MyComponent extends Component {
  static propTypes = {
    title: PropTypes.string.isRequired,
    count: PropTypes.number,
    user: PropTypes.object,
    onClick: PropTypes.func,
    children: PropTypes.element
  }

  static defaultProps = {
    count: 0
  }
}

子传父(props 回调) ​

jsx
// 子组件
function Child(props) {
  return <button onClick={() => props.onChange('data')}>发送</button>
}

// 父组件
class Parent extends Component {
  handleData = (data) => {
    console.log('收到子组件数据:', data)
  }

  render() {
    return <Child onChange={this.handleData} />
  }
}
// 子组件
function Child(props) {
  return <button onClick={() => props.onChange('data')}>发送</button>
}

// 父组件
class Parent extends Component {
  handleData = (data) => {
    console.log('收到子组件数据:', data)
  }

  render() {
    return <Child onChange={this.handleData} />
  }
}

父调子方法(ref) ​

类组件 ​

jsx
// 子组件
class Child extends Component {
  myMethod = () => console.log('子组件方法')

  render() {
    return <div>Child</div>
  }
}

// 父组件
class Parent extends Component {
  childRef = React.createRef()

  handleClick = () => {
    this.childRef.current.myMethod()
  }

  render() {
    return (
      <>
        <Child ref={this.childRef} />
        <button onClick={this.handleClick}>调用子组件方法</button>
      </>
    )
  }
}
// 子组件
class Child extends Component {
  myMethod = () => console.log('子组件方法')

  render() {
    return <div>Child</div>
  }
}

// 父组件
class Parent extends Component {
  childRef = React.createRef()

  handleClick = () => {
    this.childRef.current.myMethod()
  }

  render() {
    return (
      <>
        <Child ref={this.childRef} />
        <button onClick={this.handleClick}>调用子组件方法</button>
      </>
    )
  }
}

函数组件(useImperativeHandle) ​

jsx
// 子组件
const Child = React.forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({
    myMethod: () => console.log('子组件方法')
  }))

  return <div>Child</div>
})

// 父组件
function Parent() {
  const childRef = useRef(null)

  return <Child ref={childRef} />
}
// 子组件
const Child = React.forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({
    myMethod: () => console.log('子组件方法')
  }))

  return <div>Child</div>
})

// 父组件
function Parent() {
  const childRef = useRef(null)

  return <Child ref={childRef} />
}

props.children ​

组件标签中间的内容:

jsx
<Card>这是卡片内容</Card>

function Card(props) {
  return <div className="card">{props.children}</div>
}
<Card>这是卡片内容</Card>

function Card(props) {
  return <div className="card">{props.children}</div>
}

children 作为函数(作用域插槽) ​

jsx
function List(props) {
  return <ul>{props.children}</ul>
}

function App() {
  return (
    <List>
      {(item) => <li key={item.id}>{item.name}</li>}
    </List>
  )
}
function List(props) {
  return <ul>{props.children}</ul>
}

function App() {
  return (
    <List>
      {(item) => <li key={item.id}>{item.name}</li>}
    </List>
  )
}

修改 children ​

children 只读,需先克隆再修改:

jsx
function RadioGroup(props) {
  return (
    <div>
      {React.Children.map(props.children, (child) =>
        React.cloneElement(child, { name: props.name })
      )}
    </div>
  )
}
function RadioGroup(props) {
  return (
    <div>
      {React.Children.map(props.children, (child) =>
        React.cloneElement(child, { name: props.name })
      )}
    </div>
  )
}

React.Children API ​

jsx
React.Children.map(children, fn)      // 遍历
React.Children.forEach(children, fn)  // 遍历(无返回值)
React.Children.count(children)        // 计数
React.Children.only(children)         // 验证唯一子节点
React.Children.toArray(children)      // 转数组
React.Children.map(children, fn)      // 遍历
React.Children.forEach(children, fn)  // 遍历(无返回值)
React.Children.count(children)        // 计数
React.Children.only(children)         // 验证唯一子节点
React.Children.toArray(children)      // 转数组

Context(跨层级通信) ​

新版 createContext ​

jsx
import { createContext } from 'react'

const ThemeContext = createContext('light')

// 父组件 — 提供数据
function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Child />
    </ThemeContext.Provider>
  )
}

// 子组件 — 消费数据
function Child() {
  const theme = useContext(ThemeContext)
  return <div>当前主题:{theme}</div>
}

// 或使用 Consumer
function Child() {
  return (
    <ThemeContext.Consumer>
      {(value) => <div>当前主题:{value}</div>}
    </ThemeContext.Consumer>
  )
}
import { createContext } from 'react'

const ThemeContext = createContext('light')

// 父组件 — 提供数据
function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Child />
    </ThemeContext.Provider>
  )
}

// 子组件 — 消费数据
function Child() {
  const theme = useContext(ThemeContext)
  return <div>当前主题:{theme}</div>
}

// 或使用 Consumer
function Child() {
  return (
    <ThemeContext.Consumer>
      {(value) => <div>当前主题:{value}</div>}
    </ThemeContext.Consumer>
  )
}

旧版(类组件) ​

jsx
import PropTypes from 'prop-types'

// 父组件
class Parent extends Component {
  static childContextTypes = {
    color: PropTypes.string
  }

  getChildContext() {
    return { color: 'red' }
  }

  render() {
    return <Child />
  }
}

// 子组件
class Child extends Component {
  static contextTypes = {
    color: PropTypes.string
  }

  render() {
    return <div style={{ color: this.context.color }}>内容</div>
  }
}
import PropTypes from 'prop-types'

// 父组件
class Parent extends Component {
  static childContextTypes = {
    color: PropTypes.string
  }

  getChildContext() {
    return { color: 'red' }
  }

  render() {
    return <Child />
  }
}

// 子组件
class Child extends Component {
  static contextTypes = {
    color: PropTypes.string
  }

  render() {
    return <div style={{ color: this.context.color }}>内容</div>
  }
}

传入组件 vs 传入组件名 ​

jsx
// 传入组件实例(可传 state)
<Son extra={<Branch count={this.state.count} />} />

// 传入组件类(由子组件实例化)
<Son Extra={Branch} />

// 子组件中渲染
function Son(props) {
  const Extra = props.Extra
  return (
    <div>
      {props.extra}
      <Extra />
    </div>
  )
}
// 传入组件实例(可传 state)
<Son extra={<Branch count={this.state.count} />} />

// 传入组件类(由子组件实例化)
<Son Extra={Branch} />

// 子组件中渲染
function Son(props) {
  const Extra = props.Extra
  return (
    <div>
      {props.extra}
      <Extra />
    </div>
  )
}