我有两个共享相同方法的组件,它们很可能将不再更改,如果是的话,那么这两个组件都会更改。这就是为什么我想在此处减少冗余的原因。
但是,这些方法需要绑定到
this
,因为它们访问props
和state
,例如像这样一个:
updateProductFavorites = (product_key, action) => {
Meteor.call('Accounts.updateProductFavorites', product_key, action, (err, response) => {
if (err)
makeAlert(err.reason, 'danger', 3000)
else
this.getProductsByKeys()
})
}
这两个组件非常庞大,因此我想将它们分开,即conditional rendering
不是共享方法的选择。这两个组件需要由不同的routes
来调用。我也不想从父组件传递方法,因为在这种情况下不需要父组件。
理想情况下,我想将这些方法保存在单独的文件中。但是,导入后如何将它们正确绑定到组件上?还是有完全不同的方法?
这个问题以comment的形式提出,但没有得到令人满意的回答。
编辑:我了解了什么是HOC(高阶组件)。一旦我了解了如何在具体案例中实现它们,我将发布答案。随时帮助我。我已经在下面发布了我的两个组件。
import { Meteor } from 'meteor/meteor';
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import { Session } from 'meteor/session';
import makeAlert from '../makeAlert';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
class ProductFavorites extends Component {
constructor() {
super()
this.state = {
products: [],
productDetails: true,
singleProductDetails: 0,
}
}
updateProductFavorites = (product_key, action) => {
Meteor.call('Accounts.updateProductFavorites', product_key, action, (err, response) => {
if (err)
makeAlert(err.reason, 'danger', 3000)
else
this.getProductsByKeys()
})
}
toggleProductFavorite = (product_key) => {
const { productFavorites } = this.props.user
if (productFavorites.includes(product_key))
this.updateProductFavorites(product_key, 'remove')
else
this.updateProductFavorites(product_key, 'add')
}
toggleSingleProductDetails = (order_number) => {
const { singleProductDetails: current_product } = this.state
order_number = current_product == order_number ? 0 : order_number
this.setState({singleProductDetails: order_number})
}
toggleProductDetails = () => {
this.setState((prevState) => ({productDetails: !prevState.productDetails}))
}
getProductsByKeys = () => {
Meteor.call('Products.getByProductKey', (err, response) => {
if (err)
makeAlert(err.reason, 'danger', 3000)
else
this.setState({products: response})
})
}
mapProductFavorites = () => {
const { products, productDetails, singleProductDetails } = this.state
const { productFavorites } = this.props.user
if (products.length == 0)
return <div className="alert alert-primary col-12">You haven't favorited any products at the moment.</div>
return (
products.map((product, i) => {
if (product.price_100_g_ml) {
var [euro, cent] = product.price_100_g_ml.toFixed(2).toString().split('.')
}
const { product_name, units, trading_unit, certificate, origin, order_number, supplierId } = product
const isFavorite = productFavorites.includes(`${supplierId}_${order_number}`) ? 'is-favorite' : 'no-favorite'
return (
<div className="col-lg-4" key={i}>
<div key={i} className="product-card">
<div className="card-header" onClick={() => this.toggleSingleProductDetails(order_number)}>
{product_name}
</div>
{productDetails || singleProductDetails == order_number ?
<>
<div className="card-body">
{euro ?
<>
<div className="product-actions">
<button className={`btn btn-light btn-lg product-${isFavorite}`}
onClick={() => this.toggleProductFavorite(`${supplierId}_${order_number}`)}>
<FontAwesomeIcon icon="heart"/>
</button>
</div>
<div className="price-100-g-ml">
<small>pro 100{units == 'kg' ? 'g' : 'ml'}</small><sup></sup>
<big>{euro}</big>.<sup>{cent.substring(0,2)}</sup>
</div>
</> : null}
</div>
<div className="card-footer">
<div className="row">
<div className="col-4">{trading_unit}</div>
<div className="col-4 text-center">{certificate}</div>
<div className="col-4 text-right">{origin}</div>
</div>
</div>
</> : null }
</div>
</div>)
})
)
}
componentWillMount() {
this.getProductsByKeys()
}
render() {
const { isLoading } = this.props
if (isLoading)
return null
const { productFavorites } = this.props.user
console.log(productFavorites)
return(
<div className="container app-content product-favorites">
<div className="row mt-3">
{this.mapProductFavorites()}
</div>
</div>
)
}
}
export default withTracker(() => {
return {
user: Meteor.user(),
isLoading: !Meteor.user()
}
})(ProductFavorites)
import { Meteor } from 'meteor/meteor';
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import { Session } from 'meteor/session';
import makeAlert from '../makeAlert';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
class ProductCatalog extends Component {
constructor() {
super()
this.state = {
categoriesBySupplier: [],
productsFromCategory: [],
supplierSection: {
'supplier_0': true
},
productDetails: false,
singleProductDetails: 0,
}
}
updateProductFavorites = (product_key, action) => {
Meteor.call('Accounts.updateProductFavorites', product_key, action, (err, response) => {
if (err)
makeAlert(err.reason, 'danger', 3000)
})
}
getProductsFromCategoryOfSupplier = (supplierId, category1) => {
// console.log(supplierId, category1)
Meteor.call('Products.getFromCategory.ofSupplier', supplierId, category1, (err, response) => {
if (err)
makeAlert(err.reason, "danger", 3000)
else
this.setState({productsFromCategory: response})
})
}
getProductCategories = () => {
Meteor.call('Products.getCategories', (err, response) => {
if (err)
makeAlert(err.reason, "danger", 3000)
else {
this.setState({categoriesBySupplier: response})
this.getProductsFromCategoryOfSupplier(0, response[0].category1[0])
}
})
}
productCategories = ({_id, category1}) => {
return (
category1.map((category, i) =>
<button className="btn btn-primary btn-sm mr-1 mb-1" onClick={() => this.getProductsFromCategoryOfSupplier(_id, category)} key={i}>
{category}
</button>)
)
}
productsFromCategory = () => {
const { productsFromCategory, productDetails, singleProductDetails } = this.state
let { productFavorites } = this.props.user
productFavorites = productFavorites == undefined ? [] : productFavorites
// console.log(productsFromCategory, productFavorites)
return (
productsFromCategory.map((product, i) => {
if (product.price_100_g_ml) {
var [euro, cent] = product.price_100_g_ml.toFixed(2).toString().split('.')
}
const { product_name, units, trading_unit, certificate, origin, order_number, supplierId } = product
const isFavorite = productFavorites.includes(`${supplierId}_${order_number}`) ? 'is-favorite' : 'no-favorite'
return (
<div className="col-lg-4" key={i}>
<div key={i} className="product-card">
<div className="card-header" onClick={() => this.toggleSingleProductDetails(order_number)}>
{product_name}
</div>
{productDetails || singleProductDetails == order_number ?
<>
<div className="card-body">
{euro ?
<>
<div className="product-actions">
<button className={`btn btn-light btn-lg product-${isFavorite}`}
onClick={() => this.toggleProductFavorite(`${supplierId}_${order_number}`)}>
<FontAwesomeIcon icon="heart"/>
</button>
</div>
<div className="price-100-g-ml">
<small>pro 100{units == 'kg' ? 'g' : 'ml'}</small><sup></sup>
<big>{euro}</big>.<sup>{cent.substring(0,2)}</sup>
</div>
</> : null}
</div>
<div className="card-footer">
<div className="row">
<div className="col-4">{trading_unit}</div>
<div className="col-4 text-center">{certificate}</div>
<div className="col-4 text-right">{origin}</div>
</div>
</div>
</> : null }
</div>
</div>)
})
)
}
toggleSupplierSection = (event) => {
const supplier = event.currentTarget.id
this.setState((prevState) => ({supplierSection: {[supplier]: !prevState.supplierSection[supplier]}}))
}
toggleProductDetails = () => {
this.setState((prevState) => ({productDetails: !prevState.productDetails}))
}
toggleSingleProductDetails = (order_number) => {
const { singleProductDetails: current_product } = this.state
order_number = current_product == order_number ? 0 : order_number
this.setState({singleProductDetails: order_number})
}
toggleProductFavorite = (product_key) => {
const { productFavorites } = this.props.user
if (productFavorites.includes(product_key))
this.updateProductFavorites(product_key, 'remove')
else
this.updateProductFavorites(product_key, 'add')
}
supplierSection = (supplier) =>
<>
{this.productCategories(supplier)}
{<div className="row mt-3">{this.productsFromCategory()}</div>}
</>
mapSupplierSections = () => {
const { categoriesBySupplier, supplierSection } = this.state
if (categoriesBySupplier.length < 1)
return null
return categoriesBySupplier.map(supplier => {
var icon = 'caret-up'
var supplierId = supplierSection["supplier_" + supplier._id]
if (supplierId != undefined) {
var expand = supplierSection["supplier_" + supplier._id]
icon = expand ? 'caret-up' : 'caret-down'
}
return (
<div key={supplier._id} className="col-12">
<div className="input-group input-group-lg mb-3">
<div className="input-group-prepend">
<span className="input-group-text supplier-name">{supplier.supplierName}</span>
</div>
<div className="input-group-append">
<button className="btn btn-secondary" id={"supplier_" + supplier._id} onClick={this.toggleSupplierSection}>
<FontAwesomeIcon icon={icon} className="toggle-supplier-section"/>
</button>
<button className="btn btn-primary" id={"supplier_" + supplier._id} onClick={this.toggleProductDetails}>
<FontAwesomeIcon icon='th-list' className="toggle-supplier-section"/>
</button>
</div>
</div>
{expand
? this.supplierSection(supplier)
: null
}
</div>
)
})
}
componentWillMount() {
this.getProductCategories()
}
render() {
const { isLoading } = this.props
if (isLoading)
return null
return (
<div className="container app-content product-catalog">
{this.mapSupplierSections()}
</div>
)
}
}
export default withTracker(() => {
return {
user: Meteor.user(),
isLoading: !Meteor.user()
}
})(ProductCatalog)
答案 0 :(得分:0)
自React 16.8
起,我们有了一个名为hooks
的新解决方案,确切地说是自定义钩子。自定义钩子允许我们将有状态组件逻辑提取到函数中,然后在其他组件中重用它。但是,它们仅限于功能组件。不幸的是,您不能在类组件中调用hook函数。
(所以这对我来说不是一个解决方案,因为我已经在这里编写了类组件,并且现在我不会重写它们。我必须坚持使用HOC)
您可以在official docs中了解有关钩子的更多信息。听起来很有希望,而且似乎是React的未来方法。