學習React不久,以爲實戰纔是檢驗本身學習程度的最好方法,也順便加深一下本身對React的理解,因而作了這麼一個小項目分享一下。html
|-react-kitchen 項目名
|-node_modules 依賴包
|-public
|-src
|-api 請求數據接口
|-components 組件目錄
|-CardList 卡片列表組件
|-Footer 底部組件
|-Header 頭部組件
|-NavLeft 左側導航
|-NavRight 右側標籤
|-config 菜單配置
|-pages 頁面
|-Collections 收藏頁
|-Detail 詳情頁
|-Home 首頁
|-Search 搜索頁
|-NoMatch 無數據頁
|-。。。 其餘導航頁
|-redux redux數據管理
action-types
actions
reducers
store
|-utils 工具類
admin.js 頁面外層結構
App.js 頁面路由
common.less 頁面樣式
index.js 入口文件
config-overrides.js antd主題設置
packjon.json 全局配置
README.md readme文件
複製代碼
做爲一個單頁面項目,第一步固然是搭建頁面路由了,由於是一個菜譜項目,因此路由仍是比較多的,這裏我把路由的結構都放在config文件下,在NavLeft導航組件下用map函數去將菜單渲染出來,這樣既避免了本身一個一個去寫重複的代碼,也方便後面添加新的導航。
node
實現代碼:react
import React from 'react';
import { Menu} from 'antd';
import { NavLink } from 'react-router-dom'
import MenuConfig from '../../config/menuConfig'
const SubMenu = Menu.SubMenu;
export default class NavLeft extends React.Component {
componentWillMount() {
const menuTreeNode = this.renderMenu(MenuConfig);
this.setState({
menuTreeNode
})
}
// // 菜單渲染
renderMenu = (data) => {
return data.map((item) => {
if (item.children) {
return (
<SubMenu title={item.title} key={item.key}>
{this.renderMenu(item.children)}
</SubMenu>
)
}
return <Menu.Item title={item.title} key={item.key}>
<NavLink to={item.key}>{item.title}</NavLink>
</Menu.Item>
})
}
render() {
return (
<div>
<Menu
onClick={this.handleClick}
>
{this.state.menuTreeNode}
</Menu>
</div>
)
}
}
複製代碼
菜譜的預覽圖用的是antd的Card組件,頁面剛開始加載的時候向API請求不少組數據,並且幾乎每一個導航頁用到的列表都是同樣的,這裏就應該把整個列表抽取出來成爲一個組件進行復用。ios
先從接口中獲取數據列表git
getMenuAPIList = (keyword) => {
const num = 12
Axios
.jsonp({
url: `http://api.jisuapi.com/recipe/search?keyword=${keyword}&num=${num}&appkey=9d1f6ec2fd2463f7`
})
.then(res => {
if (res.status === '0') {
let cardList = this.renderCardList(res.result.list)
this.setState({
cardList: cardList
})
}
})
}
複製代碼
再調用數據渲染列表頁,這裏須要注意的是,渲染完預覽圖以後,點擊進入到詳情頁如何獲取當前的的數據去渲染詳情頁呢?
我想到了三種思路:github
這裏我用的是第二種方式json
// 渲染卡片列表
renderCardList = (data) => {
return data.map((item) => {
return (
<NavLink key={item.id} to={{
pathname: `/common/detail/${item.id}`,
state: item
}} >
<Card
hoverable
className="card"
cover={<img alt="example" src={item.pic} />}
onClick={this.openMenuDetail}
id={item.id}
>
<Meta
style={{ whiteSpace: 'nowrap' }}
title={item.name}
description={item.tag}
/>
</Card>
</NavLink>
)
})
}
複製代碼
上面咱們說到,能夠用link攜帶參數進行組件之間的通訊,這裏的搜索功能我是用redux進行組件之間的數據傳輸,也就是將輸入框的value值傳給搜索頁組件,讓它拿到value值後去向API請求數據。
redux
const store = createStore(reducer)api
export function reducer(state = 1, action) {
switch (action.type) {
case TRANSMIT:
return action.data
default:
return state
}
}
複製代碼
export const transmit = (data) => {
return { type: TRANSMIT, data: data }
}
複製代碼
ReactDOM.render(<Provider store={store} ><App /></Provider>, document.getElementById('root'));
複製代碼
export default connect(
state => ({keyword: state}),
{transmit}
)(Header)
export default connect(
state => ({keyword : state}),
{}
)(Search)
複製代碼
因爲本身對redux瞭解並非很深,因此這裏過程講的有點繁瑣,簡單地分享本身的一點理解,小夥伴能夠去看看阮一峯老師的 redux教程,講的很是細緻bash
收藏夾功能主要是用localStorage實現,主要的思路是:點擊收藏時,判斷數據在localstorage中是否存在,不存在,先將數據用JSON.stringify()轉化爲字符串存進localStorage,localstorage.setItem(),存在則localstorage.removeItem()取消收藏
handleCollect = () => {
let starColor = this.state.starColor
let isCollect = this.state.isCollect
const menu = JSON.stringify(this.state.menu)
const menuName = this.state.menu.name
if (isCollect === false) {
starColor = '#FDDA04'
isCollect = !isCollect
localStorage.setItem(menuName, menu)
} else {
starColor = '#52c41a'
isCollect = !isCollect
localStorage.removeItem(menuName)
}
this.setState({
starColor,
isCollect
})
message.success((isCollect ? '收藏成功' : '取消收藏'), 1)
}
複製代碼
點擊搜索實現路由跳轉 由於antd把輸入框和按鈕封裝了 若是用link包裹Search,沒輸入文字就會直接跳轉
解決辦法:不用Input.Search, 直接用input輸入框+Button按鈕,在Button的點擊事件中獲取input的value值,再用Link包裹按鈕進行路由跳轉。這是我想到的辦法,若是還有更好的解決辦法,也歡迎小夥伴提出~
啓用react-redux管理數據,在頁面第一次渲染的時候用componentWillMount請求api接口函數,將狀態進行傳參用的是this.props.keyword,以後的搜索渲染頁面的時候用的鉤子函數是componentWillReceiveProps,這個時候傳遞的參數是nextProps.keyword,而不是this.props.keyword
<br />
時沒法正確顯示緣由: react的JSX 防注入攻擊XSS使得大括號裏的html代碼所有變成字符串進行渲染,而不是html代碼
解決:使用標籤屬性dangerouslySetInnerHTML
<div dangerouslySetInnerHTML={{__html: code}}></div>
複製代碼
寫項目的時候也遇到了許多小問題,都是慢慢查文檔一個一個解決的,不斷的思考而後解決問題也是成長的一部分。
固然,項目還有許多須要完善的地方,若是發現有錯誤或者不足的地方,也但願你們可以指點一二
最後的最後,厚顏無恥地求一個STAR😋