React next.js基础教程之制作马云模拟器

Next.js简介
Next.js 是一个轻量级的 React 服务端渲染应用框架。
完善的React项目架构,搭建轻松。比如:Webpack配置,服务器启动,路由配置,缓存能力,这些在它内部已经完善的为我们搭建完成了。
自带数据同步策略,解决服务端渲染最大难点。把服务端渲染好的数据,拿到客户端重用,这个在没有框架的时候,是非常复杂和困难的。有了Next.js,它为我们提供了非常好的解决方法,让我们轻松的就可以实现这些步骤。
丰富的插件帮开发人员增加各种功能。每个项目的需求都是不一样的,包罗万象。无所不有,它为我们提供了插件机制,让我们可以在使用的时候按需使用。你也可以自己写一个插件,让别人来使用。
灵活的配置,让开发变的更简单。它提供很多灵活的配置项,可以根据项目要求的不同快速灵活的进行配置。
目前Next.js是React服务端渲染的最佳解决方案,所以如果你想使用React来开发需要SEO的应用,基本上就要使用Next.js。


项目大概样式

首先安装
yarn add react react-dom next
安装脚手架工具
npm install -g create-next-app
使用脚手架工具,通过npx方式创建
npm install -g npx
npx create-next-app next-test
全部安装完成后,可以进入项目母乳,执行yarn dev来测试项目,默认地址http://localhost:3000/
配置忽略文件:

image.png

pages下面新建index.js文件,next会自动添加路由。如果添加多个页面,便可以通过<Link>跳转,引入便可以使用。

import Link from 'next/link'

export default ()=>(
    <>
        <Link href="/"><a>返回首页</a></Link>
    </>
)

由于本项目为单页,并要配置到GitHub Pages,只建了一个页面,通过传统的react 组件方式调用。
首页jsx结构:


image.png

拆出Nav作为导航:

import Head from 'next/head'
import "../public/less/nav.less"
import React, { Component } from "react";

class Nav extends Component {
    constructor(props) {
        super(props);        
    }
    render() {
        const { value, onIncreaseClick } = this.props
        return (
            <>
                <Head>
                    <title>马云模拟器</title>
                    <meta charSet='utf-8' />
                </Head>
                <div className="navclass">
                    <h3>余额:{value}</h3>
                    {/* <button type="button">购物车</button> */}
                </div>
            </> 
        )
    }
}
export default Nav

next/head允许你在<Head></Head>标签中定义必要的信息,改变跳转后信息显示。
{value}为redux传入,下面会介绍。


image.png

列表主要拆出三个组件,用于数据的显示(其实一个就行,但是我只能传GitHub Pages上静态页面,所有数据都放一起很乱,所以就拆成了三个):


image.png

image.png

通过taplist控制组件显示。
新建并引入组件:
image.png

组件中import axios from 'axios'

    componentWillMount(){
        let _this = this;
        const promise =new Promise((resolve)=>{
            axios('../automobile/list.json').then(
                (res)=>{
                    resolve(res.data)
                }
            )
        })
        promise.then(function(res){
            let newarr = res.map(function(item){
                return {...item,addnum: 0}
            })
            _this.setState({
                list: newarr 
            })
        })}

componentWillMount中请求后台数据,并setState到当前组件中(由于没有后台,本项目数据写死)数据结构如下:

 this.setState({
            list:[
                {
                    "img":"images/zhengjiao.jpg",
                    "name":"蒸饺",
                    "number":"6",
                    "addnum": 0
                },
                {
                    "img":"images/huasheng.jpg",
                    "name":"花生米",
                    "number":"10",
                    "addnum": 0
                },    
                {
                    "img":"images/zhacai.jpg",
                    "name":"榨菜",
                    "number":"12",
                    "addnum": 0
                },
                {
                    "img":"images/naicha.jpg",
                    "name":"奶茶",
                    "number":"25",
                    "addnum": 0
                },
                {
                    "img":"images/zhurou.jpg",
                    "name":"猪肉",
                    "number":"30",
                    "addnum": 0
                },
                {
                    "img":"images/fangtuo.jpg",
                    "name":"防脱洗发液",
                    "number":"70",
                    "addnum": 0
                }
            ]
        })

之后循环数据生成列表:

    addlist(){
        let arr;
        if(this.state.list.length){
        arr = this.state.list.map((item,i) =>{
            return (<div className="auto_list" key={i}>
                    <img src={item.img}/>
                    <h3>{item.name}<span className="price">¥{item.number}</span></h3>
                    <div className="add_button">
                        <div className="control">
                            <div className="subtraction" onClick={()=>this.reducecar(i)}>
                                -
                            </div>
                            <div className="addnum">{item.addnum}</div>
                            <div className="addition" onClick={()=>this.addcar(i)}>
                                +
                            </div>
                        </div>
                        <button className="increase" onClick={()=>this.addcar(i)}>加入购物车</button>
                    </div>
                </div>)
            })
        }
        return arr;
    }
    render() {
        return (
            <>         
                <div>{this.addlist()}</div>
            </> 
        );
    }

点击购物车及加号执行addcar(i)并将点击的第几个传进去:

     addcar(i){
        let newaddnum = this.state.list[i].addnum+=1;
        let newmoney = this.state.list[i].number;
        let newprodata = this.state.value - (newaddnum*newmoney);
        this.onIncreaseClick(newprodata);
        let newlist = Object.assign([],this.state.list);
        this.setState({
            list:newlist
        })
    }

让当前列表下点击的产品个数+=1,Object.assign一个新数据,并setState到当前数据中。onIncreaseClick为redux方法,总钱数减去当前产品总钱数,然后执行此方法到父级中。

index中引入redux,做全局的数据管理:

import { createStore } from 'redux';
import { Provider,connect} from "react-redux";
//子组件传过来的值
let childrendata;
/*-----------具体通知描述及数据处理方法部分-------------*/
// Action通知及描述
const increaseAction = { type: 'reduce' }
// Reducer计算  基于原有state根据action得到新的state
function counter(state = { count: 279000000000}, action) {
  const count = state.count;

  switch (action.type) {
    case 'reduce'://如果接到action为increase的通知执行
      return { count: childrendata}
    default:
      return state//返回新的state
  }
}

/*-----------数据存储器部分-------------*/
// 根据reducer函数通过createStore()创建store(存储器)
const store = createStore(counter)

/*-----------映射方法及数据部分-------------*/
//  将state映射到Counter组件的props(数据)
function mapStateToProps(state) {
  return {
    value: state.count
  }
}
//  将action映射到Counter组件的props(方法)
function mapDispatchToProps(dispatch) {
  return {
    onIncreaseClick: function (data) {
      childrendata = data;
      dispatch(increaseAction)//定义点击方法发送action
    }
  }
}
// 传入上面两个函数参数,将Counter组件变为App组件
const App = connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter)

const Home = () => {
  return(
    <Provider store={store}>
      <App/>
    </Provider>
  )
}
export default Home

使用<Provider store={store}>将数据绑定到全局,便于子组件获取。connect()将映射方法传入,绑定到刚才生成的组件上,生成新的对象Home,并export default Home。onIncreaseClick方法触发dispatch(increaseAction)更新状态,并将子组件传过来的数据存为childrendata。
store方法counter中传两个值,state中count默认为279000000000,action为宣发室大爷。

switch (action.type) {
    case 'reduce'://如果接到action为increase的通知执行
      return { count: childrendata}
    default:
      return state//返回新的state
  }

循环,如果为张大爷通知的,则返回子组件传过来的新数据。
项目预览地址:https://aotuotage.github.io/next-test/
代码地址:https://github.com/aotuotage/next-test

配置到github pages需要在根目录下,新建next.config.js,解析less等。

const withLess = require('@zeit/next-less');
const WithCss = require('@zeit/next-css');

// fix: prevents error when .less files are required by node
if (typeof require !== 'undefined') {
  require.extensions['.less'] = file => {};
}

const prod = process.env.NODE_ENV === 'production';
module.exports = withLess(
  WithCss({
    lessLoaderOptions: {
      modifyVars: {
        'primary-color': '#1DA57A'
      },
      javascriptEnabled: true
    },
    // next-antd-ssr这个名字是你github项目名称
    assetPrefix: prod ? '/next-test' : '',
    publicRuntimeConfig: {
      linkPrefix: prod ? '/next-test' : '',
      staticFolder: '/public',
    }
  })
);

还需要配置bable,新建.babelrc。

{
    "presets": ["next/babel"],
    "plugins": [
      // 可以使用装饰器decorator
      ["@babel/plugin-proposal-decorators", { "legacy": true }], 
      // 让我们可以使用根路径,避免相对路径的混乱,如import Head from '@/components/Head'
      [
        "module-resolver",
        {
          "alias": {
            "@": "./"
          }
        }
      ],
      // 按需加载并且可以使用less的配置
      [
        "import",
        {
          "libraryName": "antd",
          "style": true
        }
      ]
    ]
  }

package.json中配置快捷命令:

"scripts": {
    "dev": "next -p 3006",
    "start": "next start -p 3006",
    "build": "next build",
    "export": "next build && next export && serve out",
    "github": "rm -rf node_modules/.cache && next build && next export && touch out/.nojekyll && git add out/ && git commit -m \"Deploy Next.js to gh-pages\" && git subtree push --prefix out origin gh-pages"
  }

执行yarn build之后再执行yarn github便可以将项目配置到github pages中了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351