我是React JS的新手,我需要构建这个简单的UI。我基本上有一个类别列表,如果我点击一个类别,项目列表将显示在该类别下。如果我点击另一个类别,它将隐藏项目列表。
我提供了两个API,一个包含类别的JSON,另一个包含项目。
我设法从API中获取数据并将其吐出DOM。但是我发现很难将组件拼凑在一起,只在单击类别时显示正确的项目。
我正在使用Babel来转换我的JSX语法并使用axios来获取数据。目前我的页面只会吐出所有项目和所有类别。理解国家对我来说很难。
对新手Reactjs更精简的任何建议?谢谢!
我的两个API可以在我的代码中找到,因为我没有足够的重复点来发布链接。
我的JSX:
var React = require('react');
var ReactDOM = require('react-dom');
var axios = require('axios');
var NavContainer = React.createClass({
getInitialState: function() {
return {
category: [],
items: []
}
},
// WHAT IS CURRENTLY SELECTED
handleChange(e){
this.setState({data: e.target.firstChild.data});
},
componentDidMount: function() {
// FETCHES DATA FROM APIS
var th = this;
this.serverRequest =
axios.all([
axios.get('https://api.gousto.co.uk/products/v2.0/categories'),
axios.get('https://api.gousto.co.uk/products/v2.0/products?includes[]=categories&includes[]=attributes&sort=position&image_sizes[]=365&image_sizes[]=400&period_id=120')
])
.then(axios.spread(function (categoriesResponse, itemsResponse) {
//... but this callback will be executed only when both requests are complete.
console.log('Categories', categoriesResponse.data.data);
console.log('Item', itemsResponse.data.data);
th.setState({
category: categoriesResponse.data.data,
items : itemsResponse.data.data,
});
}));
},
componentWillUnmount: function() {
this.serverRequest.abort();
},
render: function() {
return (
<div className="navigation">
<h1>Store Cupboard</h1>
<NavigationCategoryList data={this.state.category} handleChange={this.handleChange}/>
<NavigationSubCategoryList data={this.state.category} subData={this.state.items} selected_category={this.state.data} />
</div>
)
}
});
var NavigationCategoryList = React.createClass({
render: function () {
var handleChange = this.props.handleChange;
// LOOPS THE CATEGORIES AND OUTPUTS IT
var links = this.props.data.map(function(category) {
return (
<NavigationCategory title={category.title} link={category.id} handleChange={handleChange}/>
);
});
return (
<div>
<div className="navigationCategory">
{links}
</div>
</div>
);
}
});
var NavigationSubCategoryList = React.createClass({
render: function () {
var selected = this.props.selected_category;
var sub = this.props.subData.map(function(subcategory) {
if(subcategory.categories.title === selected)
return (
<NavigationSubCategoryLinks name={subcategory.title} link={subcategory.link} />
);
});
return (
<div className="subCategoryContainer">
{sub}
</div>
);
}
});
var NavigationSubCategoryLinks = React.createClass({
render: function () {
return (
<div className="navigationSubCategory" id={this.props.name}>
{this.props.name}
</div>
);
}
});
var NavigationCategory = React.createClass({
render: function () {
var handleChange = this.props.handleChange;
return (
<div className="navigationLink">
<a href={this.props.link} onClick={handleChange}>{this.props.title}</a>
</div>
);
}
});
ReactDOM.render(<NavContainer />, document.getElementById("app"));
以下是我目前在网页上的内容截图。一切都只是在屏幕上转储。蓝色链接是类别。
答案 0 :(得分:8)
我相信我有适合你的版本。为了清晰起见,我更改了一些语法和变量/道具名称,并添加了解释更改的注释。
const React = require('react');
const ReactDOM = require('react-dom');
const axios = require('axios');
// These should probably be imported from a constants.js file
const CATEGORIES_ENDPOINT = 'https://api.gousto.co.uk/products/v2.0/categories';
const PRODUCTS_ENDPOINT = 'https://api.gousto.co.uk/products/v2.0/products?includes[]=categories&includes[]=attributes&sort=position&image_sizes[]=365&image_sizes[]=400&period_id=120';
const NavContainer = React.createClass({
// All your state lives in your topmost container and is
// passed down to any component that needs it
getInitialState() {
return {
categories: [],
items: [],
selectedCategoryId: null
}
},
// Generic method that's used to set a selectedCategoryId
// Can now be passed into any component that needs to select a category
// without needing to worry about dealing with events and whatnot
selectCategory(category) {
this.setState({
selectedCategoryId: category
});
},
componentDidMount() {
this.serverRequest = axios.all([
axios.get(CATEGORIES_ENDPOINT),
axios.get(PRODUCTS_ENDPOINT)
])
.then(axios.spread((categoriesResponse, itemsResponse) => {
console.log('Categories', categoriesResponse.data.data);
console.log('Item', itemsResponse.data.data);
// This `this` should work due to ES6 arrow functions
this.setState({
categories: categoriesResponse.data.data,
items : itemsResponse.data.data
});
}));
},
componentWillUnmount() {
this.serverRequest.abort();
},
render() {
// ABD: Always Be Destructuring
const {
categories,
items,
selectedCategoryId
} = this.state;
return (
<div className="navigation">
<h1>
Store Cupboard
</h1>
<NavigationCategoryList
categories={categories}
// Pass the select function into the category list
// so the category items can call it when clicked
selectCategory={this.selectCategory} />
<NavigationSubCategoryList
items={items}
// Pass the selected category into the list of items
// to be used for filtering the list
selectedCategoryId={selectedCategoryId} />
</div>
);
}
});
const NavigationCategory = React.createClass({
// Prevent natural browser navigation and
// run `selectCategory` passed down from parent
// with the id passed down from props
// No querying DOM for info! when props have the info we need
handleClick(e) {
const { id, selectCategory } = this.props;
// Handle the event here instead of all the way at the top
// You might want to do other things as a result of the click
// Like maybe:
// Logger.logEvent('Selected category', id);
e.preventDefault();
selectCategory(id);
},
render() {
const { id, title } = this.props;
return (
<div className="navigationLink">
<a href={id} onClick={this.handleClick}>
{title}
</a>
</div>
);
}
});
const NavigationCategoryList = React.createClass({
// If you put your mapping method out here, it'll only
// get instantiated once when the component mounts
// rather than being redefined every time there's a rerender
renderCategories() {
const { selectCategory, categories } = this.props;
return categories.map(category => {
const { id, title } = category;
return (
<NavigationCategory
// Every time you have a list you need a key prop
key={id}
title={title}
id={id}
selectCategory={selectCategory} />
);
});
},
render() {
return (
<div>
<div className="navigationCategory">
{this.renderCategories()}
</div>
</div>
);
}
});
const NavigationSubCategoryLink = React.createClass({
render() {
const { name } = this.props;
return (
<div className="navigationSubCategory" id={name}>
{name}
</div>
);
}
});
const NavigationSubCategoryList = React.createClass({
renderSubCategories() {
const { selectedCategoryId, items } = this.props;
// This is the key to filtering based on selectedCategoryId
return items.filter(item => {
// Checking all the categories in the item's categories array
// against the selectedCategoryId passed in from props
return item.categories.some(category => {
return category.id === selectedCategoryId;
});
})
// After filtering what you need, map through
// the new, shorter array and render each item
.map(item => {
const { title, link, id } = item;
return (
<NavigationSubCategoryLink
key={id}
name={title}
link={link} />
);
});
},
render() {
return (
<div className="subCategoryContainer">
{this.renderSubCategories()}
</div>
);
}
});
ReactDOM.render(<NavContainer />, document.getElementById('app'));
此处过滤的两个关键部分是数组上的.filter()和.some()方法。
return items.filter(item => {
return item.categories.some(category => {
return category.id === selectedCategoryId;
});
})
这说的是:遍历所有items
。对于每个item
,遍历其categories
并检查其id
中的任何一个是否与selectedCategoryId
相同。如果其中一个是,则.some()
语句将返回true
,导致item
中的.filter()
返回true
,导致其在最终返回,过滤,.filter()
返回的数组。
您还会注意到我在List
组件上创建了用于映射列表项的命名方法。这样,函数只在组件安装时才会声明一次,并且每次组件重新渲染时都不会重新声明。我认为它也读得更好,并为代码添加了更多的语义。
编辑:我注意到你正在使用Babel,所以我ES6了一下。 &lt; 3 ES6。
答案 1 :(得分:4)
显然,有很多方法可以达到你想要的效果。
但这里有一个例子,我将如何个人布局这样的简单UI。我删除了API调用,以便在下面的CodePen中提供可行的示例
class Nav extends React.Component {
constructor () {
super();
this.state = {
categories: [
{ title: 'First Category', id: 0 },
{ title: 'Second Category', id: 1 },
{ title: 'Third Category', id: 2 }
],
items: [
{ title: 'Item 1', id: 0, category: { id: 0 } },
{ title: 'Item 2', id: 1, category: { id: 0 } },
{ title: 'Item 3', id: 2, category: { id: 0 } },
{ title: 'Item 4', id: 3, category: { id: 1 } },
{ title: 'Item 5', id: 4, category: { id: 1 } },
{ title: 'Item 6', id: 5, category: { id: 2 } },
{ title: 'Item 7', id: 6, category: { id: 2 } }
],
selectedCategoryId: null
};
this.onSelectCategory = this.onSelectCategory.bind(this);
}
onSelectCategory(id) {
this.setState({
selectedCategoryId: id
});
}
render() {
const { categories, items, selectedCategoryId } = this.state;
const deafultCategory = _.first(categories);
const selectedCategory = _.find(categories, i => i.id === selectedCategoryId) || deafultCategory;
return (
<div>
<CategoryFilter categories={categories} onSelectCategory={this.onSelectCategory} />
<ItemList items={items} selectedCategory={selectedCategory} />
</div>
);
}
}
var CategoryFilter = ({ categories, onSelectCategory}) => {
const links = categories.map(i => (
<div key={i.id}>
<a href="#" onClick={() => onSelectCategory(i.id)}>
{ i.title }
</a>
</div>
));
return (
<div>
{ links }
</div>
)
};
var ItemList = ({items, selectedCategory}) => {
const currentItems = items
.filter(i => i.category.id === selectedCategory.id)
.map(i => (
<div key={i.id}>
{ i.title }
</div>
));
return (
<div>
{ currentItems }
</div>
);
};
ReactDOM.render(<Nav />, document.getElementById("app"));
http://codepen.io/chadedrupt/pen/pbNNVO
希望它很有说服力。
请注意。使用了很多ES6的东西,我觉得它真的值得学习,因为它让一切都变得更加愉快。同时混合了一些Underscore / Lodash。