Эффективный способ обмена методами, связанными с этим, между компонентами React - PullRequest
0 голосов
/ 31 мая 2019

У меня есть два компонента, которые используют одни и те же методы, которые, скорее всего, больше не изменятся, и если так, то они изменятся для обоих компонентов. Вот почему я хотел бы уменьшить избыточность здесь.

Но эти методы необходимо связать с 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. Я также не хочу передавать методы из родительского компонента, так как в этом случае родительский компонент не нужен.

В идеале я хотел бы сохранить методы в отдельном файле. Но как я могу правильно связать их с компонентом после их импорта? Или есть совершенно другой подход?

Этот вопрос был задан как комментарий , но не получил удовлетворительного ответа.


Редактировать: Я узнал, что такое 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)

1 Ответ

0 голосов
/ 16 июня 2019

Так как React 16.8 у нас есть новое решение под названием hooks, точнее, пользовательские хуки.Настраиваемые хуки позволяют нам извлекать логику компонентов с состоянием в функцию, а затем повторно использовать ее в других компонентах.Однако они ограничены функциональными компонентами.К сожалению, вы не можете вызвать функцию перехвата в компоненте класса.

(Так что это не решение для меня, так как я написал здесь компоненты класса, и пока не буду их переписывать.Мне придется придерживаться HOCs)

Подробнее о хуках вы можете прочитать в официальных документах .Звучит очень многообещающе и похоже на будущий способ создания React.

...