【发布时间】:2014-09-27 21:43:59
【问题描述】:
我正在查看React.js 并试图弄清楚这个库如何与Isotope.js 一起工作。 React 的文档说它可以很好地与其他库一起使用,但是将它与自行更改 DOM 的库一起使用似乎没有使用 React 的意义。
有人可以向我解释一下,我如何在使用 Isotope.js 作为布局的 web 应用中利用 React?
【问题讨论】:
标签: javascript jquery-isotope reactjs
我正在查看React.js 并试图弄清楚这个库如何与Isotope.js 一起工作。 React 的文档说它可以很好地与其他库一起使用,但是将它与自行更改 DOM 的库一起使用似乎没有使用 React 的意义。
有人可以向我解释一下,我如何在使用 Isotope.js 作为布局的 web 应用中利用 React?
【问题讨论】:
标签: javascript jquery-isotope reactjs
我使用 useRef、useState 和 useEffect 挂钩的解决方案。它还适用于动态生成的过滤器键和项目。诀窍是在组件挂载后初始化 Isotope,并在每次过滤器关键字更改时调用其“排列”方法。
演示: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.destroy()
}, [])
// 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>
</>
)
}
更新(17/11/21):
TypeScript 演示:https://codesandbox.io/s/react-isotope-typescript-i9x5v
别忘了添加 @types/isotope-layout 作为开发依赖项。
【讨论】:
更新了休伯特对 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
编辑:几个月不使用后回到同位素。使用上述容器组件并不是同位素的最佳实践。同位素对象上存在需要的函数,你还需要在new Isotope(ref, options)函数中设置选项,如果你需要设置div的样式,回到这个组件有点尴尬。
似乎更好的做法是将代码放在此组件中,放入您正在使用同位素的任何组件中。这样您a)可以轻松访问同位素对象,b)您可以更轻松地设置容器 Div , 和 c) 您可以更轻松地传递和编辑同位素选项。
你当然可以保持容器原样,尽管有必要提升状态,这使得这个组件有点不必要。
【讨论】:
我不知道怎么做,但这对我不起作用。但是,如果我不使用地图功能并手动使用数据,这是可行的。
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;
【讨论】:
这是 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;
只需将要保留在同位素内的项目作为子项传递给 ItemGrid 组件:
<ItemGrid>
{data.map(object => (
<Item key={object._id} name={object.name} imageUrl={object.imageUrl} />
))}
</ItemGrid>
如果可以,请考虑使用react-masonry-component。
【讨论】:
我按照 Amith 在 this link 的快速教程让 Isotope 在 React 中工作。关键是解决 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>
)
}
}
它现在的工作方式与在我的静态 jQuery 网站上完全一样。如果您希望过滤器按钮在活动时更改外观,您可以简单地在 onFilterChange 函数中更新本地状态并基于此呈现按钮。
【讨论】:
这是一个使用 Masonry 的工作版本,您应该会发现它很容易移植到 Isotope(或使用 Masonry :))http://jsfiddle.net/emy7x0dc/1/。
这是使它工作的代码的症结所在(并允许 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();
}
}
});
【讨论】:
你可以直接在 React 中操作 dom。这允许集成现有的 JS 库或满足 React 无法很好处理的自定义需求。
你可以在这里找到一个例子:
这就是它的样子:
集成 React 和 Isotope 之类的库的问题在于,您最终将有 2 个不同的库尝试更新同一个 dom 子树。由于 React 使用差异,它有点假设它是单独修改 dom。
所以这个想法可能是创建一个只会渲染一次并且永远不会更新自身的 React 组件。您可以通过以下方式确保这一点:
shouldComponentUpdate: function() {
return false;
}
有了这个,你可以:
componentDidMount,初始化React挂载的dom节点上的同位素仅此而已。现在 React 再也不会更新这部分 dom,Isotope 可以随意操作它,而不会干扰 React。
此外,据我了解,Isotope 并不打算与动态项目列表一起使用,因此拥有一个永不更新的 React 组件是有意义的。
【讨论】:
您需要在 componentDidMount 上创建新的 Isotope 对象并在 componentDidUpdate 上重新加载项目。
使用我的mixin 来解决问题:)
【讨论】: