『小幫廚』- React+AntD項目實戰

前言

學習React不久,以爲實戰纔是檢驗本身學習程度的最好方法,也順便加深一下本身對React的理解,因而作了這麼一個小項目分享一下。html

技術棧

  • react
  • react-router
  • react-redux
  • less

預覽圖

基本項目搭建

  • node開發環境
  • 安裝依賴: yarn
  • 項目啓動: yarn start
  • 涉及到第三方API接口,小夥伴們能夠本身去接口地址申請一個appkey,畢竟請求次數也是有限的嘛

頁面結構

|-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>
    )
  }
}

複製代碼

CardList組件封裝

菜譜的預覽圖用的是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

  1. 將數據傳到共同的父組件,父組件經過props的方式再將數據傳給詳情頁組件
  2. 經過路由的方式,react-router v4 中 link能夠經過state的方式將參數傳遞給下一個組件,下一個組件能夠經過this.props.location.state來獲得數據
  3. 使用redux來管理數據

這裏我用的是第二種方式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

  1. 先用createStore生成一個store容器,容器接受一個純函數reducer做爲參數返回新的store

const store = createStore(reducer)api

  1. reducer接受 Action 和當前 State 做爲參數,返回一個新的 State
export function reducer(state = 1, action) {
switch (action.type) {
  case TRANSMIT:
    return action.data
  default:
    return state
  }
}
複製代碼
  1. 輸入框中的value值有無數種,也就是用戶發送的Action有無數種,能夠用一個Action Creator函數來生成Actions
export const transmit = (data) => {
  return { type: TRANSMIT, data: data }
}
複製代碼
  1. 這裏引入react-redux 用Provider將根組件包裹起來,全部的子組件默認均可以拿到state
ReactDOM.render(<Provider store={store} ><App /></Provider>, document.getElementById('root'));
複製代碼
  1. 用connect()鏈接UI組件Header和Search,connect方法接受兩個參數: mapStateToProps和mapDispatchToProps。 mapStateToProps會訂閱store,state更新時會自動執行,Search組件能夠經過this.props.keyword來拿到當前的state, mapDispatchToProps做爲對象,裏面的每一個鍵值被當作Action Creator
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 Input.Search

點擊搜索實現路由跳轉 由於antd把輸入框和按鈕封裝了 若是用link包裹Search,沒輸入文字就會直接跳轉

解決辦法:不用Input.Search, 直接用input輸入框+Button按鈕,在Button的點擊事件中獲取input的value值,再用Link包裹按鈕進行路由跳轉。這是我想到的辦法,若是還有更好的解決辦法,也歡迎小夥伴提出~

搜索頁面的從新渲染

啓用react-redux管理數據,在頁面第一次渲染的時候用componentWillMount請求api接口函數,將狀態進行傳參用的是this.props.keyword,以後的搜索渲染頁面的時候用的鉤子函數是componentWillReceiveProps,這個時候傳遞的參數是nextProps.keyword,而不是this.props.keyword

react渲染html代碼例如<br />時沒法正確顯示

緣由: react的JSX 防注入攻擊XSS使得大括號裏的html代碼所有變成字符串進行渲染,而不是html代碼

解決:使用標籤屬性dangerouslySetInnerHTML

<div dangerouslySetInnerHTML={{__html: code}}></div>
複製代碼

結語

項目傳送門

寫項目的時候也遇到了許多小問題,都是慢慢查文檔一個一個解決的,不斷的思考而後解決問題也是成長的一部分。

固然,項目還有許多須要完善的地方,若是發現有錯誤或者不足的地方,也但願你們可以指點一二

最後的最後,厚顏無恥地求一個STAR😋

相關文章
相關標籤/搜索