React组件通信方法:从props到redux、zustand

136 阅读5分钟

什么是组件?

React 组件是 React 应用的基本构建块,它允许你将 UI 拆分成独立的、可复用的部分,并对每个部分进行独立的思考。组件就像函数一样,接收任意的输入(称为 “props”),并返回用于描述页面展示内容的 React 元素。组件分为函数组件和类组件,因为类组件已经基本弃用所以本文的组件就指函数组件。

函数组件示例

下面是一个简单的函数组件示例:

import React from 'react';

// 函数组件:欢迎消息
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

// 使用箭头函数的组件
const Greeting = (props) => {
return <p>Welcome, {props.username}!</p>;
};

// 组件可以嵌套使用
function App() {
return (
  <div>
    <Welcome name="Alice" />
    <Greeting username="Bob" />
  </div>
);
}

1. 父组件传子组件

父组件直接在子组件标签上写属性,子组件通过props接收

在上面的例子中父组件App直接在子组件标签上传递属性name和username,子组件通过props接收,props(全称为 "properties")是一种从父组件向子组件传递数据的机制,类似于函数的参数。

props 的核心特点

  1. 只读性:props 是不可变的(immutable),子组件不能直接修改接收到的 props。
  2. 单向数据流:数据流动是单向的(从父组件到子组件)。
  3. 任意数据类型:props 可以传递任何数据类型(字符串、数字、对象、函数、甚至组件)。
  4. 组件复用性:通过 props 传递不同的数据,可以复用同一个组件。

props为对象,利用解构语法代码可以简化为

// 函数组件:欢迎消息
function Welcome({name}) {
  return <h1>Hello, {name}</h1>;
}

2. 子组件传父组件

父组件创建一个函数,子组件调用这个函数,并将需要的数据作为参数传递

父组件

传递给子组件setList()函数,需要获得input框输入的值并展示在列表中

import { useState} from 'react'
import Child from './Child.jsx'

export default function Parent() {
  const [list, setList] = useState(['html', 'css', 'js'])

  return (
    <div>
      <Child setList={setList}/>
      <div className="bd">
        <ul>
          {
            list.map((item) => {
              return <li key={item}>{item}</li>
            })
          }
        </ul>
      </div>
    </div>
  )
}

子组件

调用setList()函数,并将input框获得的值作为参数传递

import { useRef } from 'react'

export default function Child({ setList }) {
  const inputRef = useRef(null)

  const handler = () => {
    // 将 input 中的值添加到父组件的 list 中
    setList((preList) => {
      return [...preList, inputRef.current.value]
    })
  }

  return (
    <div className="hd">
      <input type="text" ref={inputRef}/>
      <button onClick={handler}>确认</button>
    </div>
  )
}

3. 兄弟组件之间

子传父,父传子

结合前面两种方法,先让一个兄弟传递给它们的父组件然后父组件传给另一个兄弟

兄弟组件1

传递给父组件input框输入的内容

import React from 'react'
import { useRef} from 'react'


export default function Child1({setList}) {
const inputRef = useRef(null)

  const handler = () => {
    setList((preList)=>{
        return [...preList,inputRef.current.value]
    })
  }
  
  return (
     <div className="hd">
        <input type="text" ref={inputRef}/>
        <button onClick={handler}>确认</button>
      </div>
  )
}

父组件

定义setList()函数,通过setList()得到修改的list,传递list给兄弟组件2

import Child1 from './Child1'
import Child2 from './Child2'
import { useState } from 'react'
export default function Parent() {
const [list, setList] = useState([])

  return (
    <div>
        <Child1 setList={setList}/>
        <Child2 list={list}/>  
    </div>
  )
}

兄弟组件2

接收到list并渲染在li标签中


export default function Child2({list}) {
    
   
  return (
    <div className="bd">
        <ul>
          {
            list.map((item) => {
              return <li key={item}>{item}</li>
            })
          }
        </ul>
      </div>
  )
}

4. 跨层级组件

使用useContext()钩子函数

import { useState, createContext, useContext } from "react"
const myConText = createContext()


function B() {
  const msg = useContext(myConText)
  return <div>
    <h3>BBBB --- {msg}</h3>
  </div>
}

function A() {
  const msg = useContext(myConText)
  return <div>
    <h2>AAAA --- {msg}</h2>
    <B />
  </div>
}

export default function index() {
  const [msg, setMsg] = useState("Index 组件中的数据")

  return (
    <div>
      <myConText.Provider value={msg}>
        <A />
      </myConText.Provider>
    </div>
  )
}

使用方法可以看之前介绍钩子函数的文章--- React常用Hooks函数(1):useState、useEffect、useLayoutEffect……什么是 Ho - 掘金

5. 任意组件

想要实现在任意组件间通信有很多方法,这里介绍使用状态管理仓库的方法,在仓库中储存需要传递的信息,其它组件需要信息就从仓库中取。

状态管理库(Redux/MobX/ Zustand)

原理:集中管理应用的状态,组件通过订阅和派发操作共享状态。
适用场景:大型应用的复杂状态管理。

Redux 示例

1. 安装依赖

npm install @reduxjs/toolkit react-redux

2. 创建仓库

创建一个总仓库store目录,在总仓库index.js中注册子模块,子模块创建在modules目录的文件如counterStore.js

└── store
    ├── modules
    │   └── counterStore.js
    └── index.js

index.js

//总仓库
import {configureStore} from "@reduxjs/toolkit";
import counterReducer from "./modules/counterStore.js";
export default configureStore({
    reducer:{ //注册子模块
        counter:counterReducer
    }
})

counterStore.js

import { createSlice } from "@reduxjs/toolkit";
//仓库子模块
const counter = createSlice({
  name: "counter",
  initialState: {
    list:["css"]
  },
  reducers: {
    addList(state,action){
      state.list.push(action.payload)
    }
  },
});


const { addList } = counter.actions;
export { addList };

const counterReducer = counter.reducer;
export default counterReducer;

3. 在mian.js引入

import { createRoot } from 'react-dom/client'
import App from './App.jsx'
import store from './store/index.js'
import { Provider } from 'react-redux'

createRoot(document.getElementById('root')).render(
    <Provider store={store}>
        <App />
    </Provider>
)

4. 使用数据和方法

使用useSelector()useDispatch()获取仓库中的数据和触发器

修改仓库数据:

  • 调用仓库方法,得到一个action行为
  • 调用dispatch将action行为传递给仓库

import { useSelector ,useDispatch} from 'react-redux'

import {useRef} from 'react'
import {addList} from '../../store/modules/counterStore.js'

export default function index() {
  const inputRef = useRef(null);
  //获取仓库中的数据
  const {list} = useSelector((state)=>{
    return state.counter
  })
  //修改仓库数据
  const dispatch = useDispatch()
  const addCount = () => {
    const val=inputRef.current.value
    dispatch(addList(val))
  }
  
  return (    
    <div>
      <input type="text" ref={inputRef}/>
      <button onClick={()=>{addCount()}}>添加</button>
      <ul>
        {
          list.map((item,index)=>{
            return <li key={index}>{item}</li>
          })
        }
      </ul>
    </div>
  )
}

Redux的使用过于繁琐,接下来介绍一种更简单、易上手的方法状态管理库Zustand

zustand 示例

安装依赖

npm install zustand

在store文件夹下创建components.tsx文件,定义需要在任意组件使用的数据和方法并导出

import {create} from 'zustand'

const useComponentsStore = create((set) => ({
    // 数据
    mode: 'edit',
    // 方法
    setMode: (type:any) => set(() => ({ mode :type}))
}))

export default useComponentsStore

引入仓库中的数据和方法,实现点击按钮从预览变成退出预览

import { Button } from "antd"
import useComponentsStore from "../../stores/components.tsx"

export default function Materail() {
  const mode = useComponentsStore((state: any) => state.mode)
  return (

    <div>
      {mode === 'edit' && (
        <Button type="primary" onClick={() => {
          useComponentsStore.setState({ mode: 'preview' })
        }}>预览</Button>
      )}
      {mode === 'preview' && (
        <Button type="primary" onClick={() => {
          useComponentsStore.setState({ mode: 'edit' })
        }}>退出预览</Button>
      )}
    </div>
  )
}