『小帮厨』- 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😋

相关文章
相关标签/搜索