简体   繁体   English

React.js 和 Isotope.js

[英]React.js and Isotope.js

I'm checking out React.js and trying to figure out how this library can work together with Isotope.js .我正在查看React.js并试图弄清楚这个库如何与Isotope.js一起工作。 The documentation of React says that it plays nicely with other libraries, but using it with library that changes DOM on its own seems like no sense of using React. React 的文档说它可以很好地与其他库一起使用,但是将它与自行更改 DOM 的库一起使用似乎没有使用 React 的意义。

Can someone explain to me, how can I take advantage of React in my webapp that uses Isotope.js as layout ?有人可以向我解释一下,如何在使用 Isotope.js 作为布局的 web 应用程序中利用 React?

Here's a working version with Masonry, you should find it easy enough to port to Isotope (or use Masonry :)) http://jsfiddle.net/emy7x0dc/1/ .这是 Masonry 的工作版本,您应该会发现很容易移植到 Isotope(或使用 Masonry :)) http://jsfiddle.net/emy7x0dc/1/

Here's the crux of the code that makes it work (and allow React to do its job).这是使其工作(并允许 React 完成其工作)的代码的关键。

var Grid = React.createClass({
    displayName: 'Grid',

    getInitialState: function(){
        return {
            masonry: null
        }
    },

    // Wrapper to layout child elements passed in
    render: function () {
        var children = this.props.children;
        return (
            <div className="grid">
                {children}
            </div>
        );
    },

    // When the DOM is rendered, let Masonry know what's changed
    componentDidUpdate: function() {
        if(this.state.masonry) {
            this.state.masonry.reloadItems();
            this.state.masonry.layout();
        }
    },

    // Set up Masonry
    componentDidMount: function() {
        var container = this.getDOMNode();
        if(!this.state.masonry) {
            this.setState({
                masonry: new Masonry( container )
            });
        } else {
            this.state.masonry.reloadItems();
        }
    }
});

Here's an updated version of the above code posted by James:这是 James 发布的上述代码的更新版本:

import React, { PureComponent } from 'react';
import ReactDOM from 'react-dom';
import Isotope from 'isotope-layout';

// Container for isotope grid
class ItemGrid extends PureComponent {
    constructor(props) {
        super(props);
        this.state = { isotope: null };
    }

    render() {
        return(
            <div className="item-grid">
                {this.props.children}
            </div>
        )
    }

    // set up isotope
    componentDidMount() {
        const node = ReactDOM.findDOMNode(this);
        if (!this.state.isotope) {
            this.setState({
                isotope: new Isotope( node )
            });
        } else {
            this.state.isotope.reloadItems();
        }
    }

    // update isotope layout
    componentDidUpdate() {
        if (this.state.isotope) {
            this.state.isotope.reloadItems();
            this.state.isotope.layout();
        }
    }
}

export default ItemGrid;

Usage:用法:

Just pass the items you want to keep inside isotope into the ItemGrid component as children:只需将要保留在同位素中的项目作为子项传递到 ItemGrid 组件中即可:

<ItemGrid>
    {data.map(object => (
      <Item key={object._id} name={object.name} imageUrl={object.imageUrl} />
    ))}
</ItemGrid>

Alternatives备择方案

If you can, consider using react-masonry-component .如果可以,请考虑使用react-masonry-component

My solution with useRef, useState and useEffect hooks.我的 useRef、useState 和 useEffect 钩子解决方案。 It also works with dynamically generated filter keys and items.它还适用于动态生成的过滤器键和项目。 The trick is to initialize Isotope after the component is mounted and call its "arrange" method every time the filter keyword changes.诀窍是在组件安装后初始化 Isotope 并在每次过滤器关键字更改时调用其“排列”方法。

Demo: https://codepen.io/ilovepku/pen/zYYKaYy演示: https : //codepen.io/ilovepku/pen/zYYKaYy

const IsotopeReact = () => {
  // init one ref to store the future isotope object
  const isotope = React.useRef()
  // store the filter keyword in a state
  const [filterKey, setFilterKey] = React.useState('*')

  // initialize an Isotope object with configs
  React.useEffect(() => {
    isotope.current = new Isotope('.filter-container', {
      itemSelector: '.filter-item',
      layoutMode: 'fitRows',
    })
    // cleanup
    return () => isotope.current.destory()
  }, [])

  // handling filter key change
  React.useEffect(() => {
    filterKey === '*'
      ? isotope.current.arrange({filter: `*`})
      : isotope.current.arrange({filter: `.${filterKey}`})
  }, [filterKey])

  const handleFilterKeyChange = key => () => setFilterKey(key)

  return (
    <>
      <ul>
        <li onClick={handleFilterKeyChange('*')}>Show Both</li>
        <li onClick={handleFilterKeyChange('vege')}>Show Veges</li>
        <li onClick={handleFilterKeyChange('fruit')}>Show Fruits</li>
      </ul>
      <hr />
      <ul className="filter-container">
        <div className="filter-item vege">
          <span>Cucumber</span>
        </div>
        <div className="filter-item fruit">
          <span>Apple</span>
        </div>
        <div className="filter-item fruit">
          <span>Orange</span>
        </div>
        <div className="filter-item fruit vege">
          <span>Tomato</span>
        </div>
      </ul>
    </>
  )
}

You can manipulate the dom directly inside React.你可以直接在 React 内部操作 dom。 This permits to integrate existing JS libraries or for custom needs not handled well by React.这允许集成现有的 JS 库或用于 React 无法很好处理的自定义需求。

You can find an exemple here:你可以在这里找到一个例子:

https://github.com/stample/gulp-browserify-react-phonegap-starter/blob/master/src/js/home/homeComponents.jsx#L22 https://github.com/stample/gulp-browserify-react-phonegap-starter/blob/master/src/js/home/homeComponents.jsx#L22

And here's what it looks like:这是它的样子:

图片


The problem with integration of React and a library like Isotope is that you will end up having 2 different libraries trying to update the same dom subtree. React 和 Isotope 之类的库集成的问题在于,您最终将有 2 个不同的库试图更新同一个 dom 子树。 As React work with diffs, it kind of assumes that it is alone modyfing the dom.当 React 使用 diff 时,它有点假设它是单独修改 dom 的。

So the idea could be to create a React component that will render only one time, and will never update itself.所以这个想法可能是创建一个只渲染一次的 React 组件,并且永远不会更新自己。 You can ensure this with:您可以通过以下方式确保这一点:

shouldComponentUpdate: function() { 
    return false; 
}

With this you can:有了这个,你可以:

  • Use React to generate your isotope item html elements (you can also create them without React)使用 React 生成您的同位素项目 html 元素(您也可以在没有 React 的情况下创建它们)
  • On componentDidMount , initialize isotope on the dom node mounted by ReactcomponentDidMount ,在 React 挂载的 dom 节点上初始化 isotope

And that's all.就这样。 Now React will never update this part of the dom again, and Isotope is free to manipulate it like it wants to without interfering with React.现在 React 将永远不会再更新 dom 的这部分,并且 Isotope 可以随意操作它,而不会干扰 React。

In addition, as far as I understand, Isotope is not intented to be used with a dynamic list of items so it makes sense to have a React component that never updates.此外,据我所知,Isotope 并不打算与动态项目列表一起使用,因此拥有一个永不更新的 React 组件是有意义的。

Updated Hubert's answer to modern react with Hooks.更新了休伯特对 Hooks 的现代反应的回答。

import React, { useEffect, useRef, useState } from 'react';
import Isotope from 'isotope-layout';

function IsoContainer (props) {
    const isoRef = useRef();
    const [isotope, setIsotope] = useState(null);

    useEffect(() => {
        if (isotope)
            isotope.reloadItems();
        else
            setIsotope(new Isotope( isoRef.current ));
    })

    return (
        <div ref={isoRef}>
            {props.children}
        </div>
    )
}

export default IsoContainer

Edit: Coming back to Isotope after not using for a few months.编辑:几个月不使用后回到同位素。 Using a container component as above isn't the best practice for isotope.如上所述使用容器组件并不是同位素的最佳实践。 There are functions that exist on the isotope object that are needed, you also need to set the option in the new Isotope(ref, options) function, AND if you need to style the div, it's a little awkward to come back to this component.有需要的同位素对象上存在的函数,您还需要在new Isotope(ref, options)函数中设置选项,并且如果您需要设置 div 的样式,回到这个组件有点尴尬.

It seems a better practice is to instead place the code within this component, into any component you are using isotope in. This way you a) have easy access to the isotope object, b) you can more easily style the container Div, and c) you can more easily pass and edit the isotope options.似乎更好的做法是将代码放置在此组件中,放入您使用同位素的任何组件中。这样您 a) 可以轻松访问同位素对象,b) 您可以更轻松地设置容器 Div 的样式,以及 c ) 您可以更轻松地传递和编辑同位素选项。

You can of course keep the container as it is, though it becomes necessary to lift the state up, making this component a little unnecessary.您当然可以保持容器原样,尽管有必要提升状态,使这个组件有点不必要。

You need to create new Isotope object on componentDidMount and reload items on componentDidUpdate.您需要在 componentDidMount 上创建新的 Isotope 对象并在 componentDidUpdate 上重新加载项目。

Use my mixin to figure it out :)使用我的mixin来弄清楚:)

I got Isotope working in React by following Amith's quick tutorial at this link .我在这个链接上按照 Amith 的快速教程让 Isotope 在 React 中工作。 The key was to address filtering within the onClick function:关键是解决 onClick 函数中的过滤问题:

class Parent extends Component {
  constructor(props) {
    super(props);
    this.onFilterChange = this.onFilterChange.bind(this);
  }

  // Click Function
  onFilterChange = (newFilter) => {
    if (this.iso === undefined) {
      this.iso = new Isotope('#filter-container', {
        itemSelector: '.filter-item',
        layoutMode: "fitRows"
      });
    }
    if(newFilter === '*') {
      this.iso.arrange({ filter: `*` });
    } else {
      this.iso.arrange({ filter: `.${newFilter}` });
    }
  }

  render() {
    return(
      // Filter Buttons
      <ul id="portfolio-flters">
        <li data-filter="*" onClick={() => {this.onFilterChange("*")}}>All</li>
        <li data-filter="filter-one" onClick={() => {this.onFilterChange("filter-one")}}>One</li>
        <li data-filter="filter-two" onClick={() => {this.onFilterChange("filter-two")}}>Two</li>
      </ul>

      // Isotope Grid & items
      <div id="filter-container">
        <div className='filter-item filter-one'>
          // Item Content
        </div>
        <div className='filter-item filter-two'>
          // Item Content
        </div>
      </div>
    )
  }
}

It now works exactly like it did on my static jQuery site.它现在的工作方式与在我的静态 jQuery 站点上完全一样。 If you want the filter buttons to change appearance when active you can simply update local state in the onFilterChange function and render the buttons based on that.如果您希望过滤器按钮在活动时更改外观,您可以简单地更新 onFilterChange 函数中的本地状态并基于此渲染按钮。

I don't know how but this doesn't work for me.我不知道如何,但这对我不起作用。 But if I don't use the map function and use data manually this works.但是,如果我不使用 map 功能并手动使用数据,这将起作用。

import React, {useEffect, useState, useRef} from 'react';
import options from "./Options"
import ReactDom from 'react-dom'
import Isotope from 'isotope-layout';
import ItemGrid from "./ItemGrid";


const Home = () => {
    const [question, setQuestion] = useState();
    const [options, setOptions] = useState([]);
    
    // store the isotope object in one state
    const [isotope, setIsotope] = React.useState(null);
useEffect(() => {
        Axios.get("http://localhost:8080/laravel/voting/public/api/question/3").then((res)=>{
            console.log(res.data)
            setQuestion(res.data.question);
            setOptions(res.data.option)
        });
    }, []);

    useEffect(() => {
        setIsotope(
            new Isotope(".filter-container", {
                itemSelector: ".filter-item",
                layoutMode: "vertical",
                getSortData : {number: '.number parseInt'}
            })
        );
    }, []);



    const changeStateLevel = ()=>{
        isotope.arrange({ sortBy: "number" });
    }
    return (

        <>
            <div className="row">
                <div className="col-sm-7">
                    <div className="col-sm-14 mb-sm-5" >
                        <hr />
                        <ul className="filter-container">
                            {
                                options.map(object=>(
                                        <div className="filter-item vege">
                                            <p className="number">{object.vote}</p>
                                            <span>Cucumber</span>
                                        </div>
                                ))
                            }
                        </ul>

                    </div>

                </div>
                <div className="col-sm-5">
                </div>
                <button className="btn btn-primary" onClick={changeStateLevel}> Change</button>
            </div>
        </>
    );
}

export default Home;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM