『小幫廚』- React+AntD專案實戰

小榕同學發表於2019-03-07

前言

學習React不久,覺得實戰才是檢驗自己學習程度的最好方法,也順便加深一下自己對React的理解,於是做了這麼一個小專案分享一下。

技術棧

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

預覽圖

『小幫廚』- React+AntD專案實戰

『小幫廚』- React+AntD專案實戰

『小幫廚』- React+AntD專案實戰

基本專案搭建

  • 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函式去將選單渲染出來,這樣既避免了自己一個一個去寫重複的程式碼,也方便後面新增新的導航。

實現程式碼:

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請求很多組資料,而且幾乎每個導航頁用到的列表都是一樣的,這裡就應該把整個列表抽取出來成為一個元件進行復用。

先從介面中獲取資料列表

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
          })
        }
      })
  }
複製程式碼

再呼叫資料渲染列表頁,這裡需要注意的是,渲染完預覽圖之後,點選進入到詳情頁如何獲取當前的的資料去渲染詳情頁呢?
我想到了三種思路:

  1. 將資料傳到共同的父元件,父元件通過props的方式再將資料傳給詳情頁元件
  2. 通過路由的方式,react-router v4 中 link可以通過state的方式將引數傳遞給下一個元件,下一個元件可以通過this.props.location.state來得到資料
  3. 使用redux來管理資料

這裡我用的是第二種方式

// 渲染卡片列表
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請求資料。

  1. 先用createStore生成一個store容器,容器接受一個純函式reducer作為引數返回新的store

const store = createStore(reducer)

  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教程,講的非常細緻

收藏功能

收藏夾功能主要是用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?

相關文章