发布于 2026-01-06 0 阅读
0

高级 React 模式:渲染属性

高级 React 模式:渲染属性

嗨👋

如果你使用 React 已经有一段时间了,那么你一定很擅长编写可复用组件。可复用的 UI 组件!但是随着代码库规模的扩大,你经常会遇到这样的情况:你想共享业务逻辑,但 UI 部分可能有所不同。

💡 这种情况正是运用一些高级模式的绝佳时机。渲染属性(Render Props)就是这样一种模式。

🚀 使用渲染属性模式的一些库包括 React Router、Downshift 和 Formik。

我们先来看一个例子。

例如,您正在搭建一个展示产品的在线商店。

每个产品列表都需要遵循一些通用的业务逻辑:

  1. ✈️ 点击后跳转至产品页面
  2. 🤑 按价格对产品进行排序
  3. 💾 节省产品
    • 如果用户未登录,则保存到本地存储。
    • 否则,使用 API 调用将其存储在数据库中。

🎨 但根据列表的渲染位置不同,用户界面也会有所不同。有的地方可能想显示统计数据或产品图片,有的地方可能只想显示标题。

🧠 首先,让我们了解一下渲染属性组件的基本结构。


const Wrapper = ({ products, render }) => {

  // do some stuff
  const updatedProducts = someOperations(products)

  // provide some helper funcs for data like sort func
  const sort = () => {}

  return render({ updatedProducts, sort })
}


Enter fullscreen mode Exit fullscreen mode

👉 Render Props 组件只是对 UI 组件的封装。上面的组件接收两个 props:`data`productsrender`function`。`data`products是需要使用业务逻辑修改的数据,` renderfunction` 是一个函数,它会接收修改后的数据和其他一些辅助函数。

🤔 但是我该如何使用这个组件呢?

// import everything

const HomeScreenProducts = () => {

  // assume you have this hook
  const { products } = useData()

  return (
    <ProductsWrapper 
     products={products}
     render={
       ({ updatedProducts, sort }) => updatedProducts.map(product => <ProductCard />)
     }
    />
  )

}

Enter fullscreen mode Exit fullscreen mode

👉 我们的HomeScreenProducts组件使用ProductsWrapper并处理所有业务逻辑。用户界面仍然由调用组件控制。在函数
内部,render我们使用修改后的产品数据并据此渲染用户界面。

😰 这看起来有点复杂。但我们可以简化它,使其 API 更简洁。与其render单独传递函数,不如直接使用childrenprop。

更新后,两个组件看起来是这样的。


const Wrapper = ({ products, children }) => {

  // same stuff here

  return children({ updatedProducts, sort })
}

Enter fullscreen mode Exit fullscreen mode

const HomeScreenProducts = () => {

  // same stuff here

  return (
    <ProductsWrapper products={products}>
      {({ updatedProducts, sort }) => updatedProducts.map(product => <ProductCard />}
    </ProductsWrapper>
  )

}

Enter fullscreen mode Exit fullscreen mode

👌 这样好多了。这个children道具和我们之前用的道具功能相同render。这种编写渲染道具的方式更常见。

⚠️ 别忘了添加key到你的清单中。

💪 现在我们已经了解了渲染属性模式,我们可以完成前面提到的任务了。

import React from "react";
import { useAuth } from "./hooks/useAuth";

const ProductsWrapper = ({ products, children }) => {
  const { isLoggedIn } = useAuth();
  const [sortedProducts, setSortedProducts] = React.useState(products);

  const sort = (order) => {
    const reorderedProducts = [...products];

    reorderedProducts.sort((a, b) => {
      if (order === "desc") {
        return b.price > a.price;
      } else {
        return a.price > b.price;
      }
    });

    setSortedProducts(reorderedProducts);
  };

  const save = (productId) => {
    if (isLoggedIn) {
      // call API
      console.log("Saving in DB... ", productId);
    } else {
      // save to local storage
      console.log("Saving in local storage... ", productId);
    }
  };

  const navigate = () => {
    console.log("Navigating...");
  };

  return children({ sortedProducts, sort, save, navigate });
};

export default ProductsWrapper;

Enter fullscreen mode Exit fullscreen mode

我们扩展了ProductsWrapper组件,并为其添加了所有必需的功能。它children以函数的形式调用,并传递数据和辅助函数。

import ProductsWrapper from "./ProductsWrapper";

const products = [
  { id: 1, name: "Coffee", price: 2 },
  { id: 2, name: "Choclates", price: 3 },
  { id: 3, name: "Milk", price: 5 },
  { id: 4, name: "Eggs", price: 4 },
  { id: 5, name: "Bread", price: 1 }
];

export default function App() {
  return (
    <div className="App">
      <ProductsWrapper products={products}>
        {({ sortedProducts, sort, save, navigate }) => (
          <>
            <div className="flex">
              <button onClick={() => sort("desc")}>Price: High to Low</button>
              <button onClick={() => sort("asc")}>Price: Low to High</button>
            </div>

            {sortedProducts.map((product) => (
              <div className="product" key={product.id}>
                <span onClick={() => navigate(product.id)}>
                  {product.name} - ${product.price}
                </span>
                <button className="save-btn" onClick={() => save(product.id)}>
                  save
                </button>
              </div>
            ))}
          </>
        )}
      </ProductsWrapper>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

✅ 我们的 UI 组件负责ProductsWrapper用户界面的设计和维护。如您所见,我们可以自由修改用户界面,或创建与此完全不同的其他 UI 组件。我们的业务逻辑集中在一处。

如果您想尝试一下这个示例,可以在 CodeSandbox 上找到:https://codesandbox.io/s/render-props-example-6190fb

就这些啦!👋

🤙 如果这篇文章对您有帮助,请考虑分享,也可以在LinkedInTwitter上与我联系。

文章来源:https://dev.to/thesanjeevsharma/advance-react-patterns-render-props-3hjj