react基础学习和react服务端渲染框架next.js踩坑
说明
React作为Facebook 内部开发 Instagram 的项目中,是一个用来构建用户界面的优秀 JS 库,于 2013 年 5 月开源。作为前端的三大框架之一,React的应用可以说是非常的广泛。这里讲一个react服务端渲染的框架-next.js踩坑过程。
技术栈
react、next.js、ant design、axios
大纲
按照以下思路来写:
react基本语法
react基本语法参照react文档,这里发放一个链接https://doc.react-china.org/。
以下是React重要的部分
- JSX – 允许我们编写类似HTML的语法,转换为JavaScript对象;
- 虚拟DOM – 实际DOM的JavaScript表示;
- React.Component – 创建新组件的方式;
- render(方法) – 渲染组件;
- ReactDOM.render – 用于将模板转为 HTML 语言,并插入指定的 DOM 节点;
- state – 组件的内部数据存储(对象),类型理解vue的data;
- constructor(this.state) – 建立组件初始 state(状态) 的方式;
- setState – 一种辅助方法,用于更新组件的 state(状态) 并重新渲染,类似微信小程序;
- props – 从父组件传递给子组件的数据;
- propTypes – 允许您控制传递给子组件的某些 props(属性) 的存在或类型;
- defaultProps – 允许您为组件设置默认 props(属性) ;
- className - 节点的class,css标记
- style jsx样式控制
jsx语法
直接写在 JavaScript 语言之中,不加任何引号,它允许 HTML 与 JavaScript 的混写。
//demo-1.js
import React from 'react';
class Demo1 extends React.Component{
render(){
const lists = ['我是谁','我来自哪里','我要到哪儿去'];
return(
<div className="list">
<ul>
{
lists.map((list,index)=>{
return(
<li key={index}>{list}</li>
)
})
}
</ul>
</div>
)
}
}
export default Demo1;
上面代码体现了 JSX 的基本语法规则:遇到 HTML 标签(以 < 开头),就用 HTML 规则解析;遇到代码块(以 { 开头),就用 JavaScript 规则解析,其中注意一点react没有vue中v-for遍历,需要自己写遍历。上面代码的运行结果如下:
组件及组件传值
//demo-2.js
import React from 'react';
class MyComponent extends React.Component{
render(){
return (
<h1>{this.props.name}</h1>
)
}
}
class Demo2 extends React.Component{
render(){
const lists = ['我是谁','我来自哪里','我要到哪儿去'];
return(
<div className="list">
<ul>
{
lists.map((list,index)=>{
return(
<li key={index}>
<MyComponent name={list}></MyComponent>
</li>
)
})
}
</ul>
</div>
)
}
}
export default Demo2;
React 允许将代码封装成组件(component),然后像插入普通 HTML 标签一样,在网页中插入这个组件。以上代码中MyComponent即为组件,需要注意的一点组件类的第一个字母必须大写,否则会报错,组件类只能包含一个顶层标签,否则也会报错。vue通过子组件中的props来传递数据,而React则是用this.props.name来传递。下图为渲染页面效果:
React之ref(获取真实的DOM节点)
- 同vue的ref作用一样,组件并不是真实的DOM节点,而是存在于内存之中的一种数据结构,叫做虚拟 DOM。
- 只有当它插入文档以后,才会变成真实的 DOM 。根据 React的设计,所有的DOM 变动,都先在虚拟 DOM上发生,然后再将实际发生变动的部分,反映在真实 DOM上,这种算法叫做 DOM diff ,它可以极大提高网页的性能表现。
- 有时需要从组件获取真实 DOM 的节点,这时就要用到 ref 属性。
//dem0-3.
import React from 'react';
class Demo3 extends React.Component{
handleClick(){
this.refs.myTextInput.focus();
}
render(){
return(
<div>
<input type="text" ref="myTextInput" />
<br/>
<input type="button" value="Focus the text input" onClick={this.handleClick.bind(this)} />
</div>
)
}
}
export default Demo3;
在这里需要注意的是:
- React的事件中如果不用剪头函数,那就要用bind来绑定this,否则需要在constructor构造函数里调用this.handleClick = this.handleClick.bind(this),这里建议用onClick={this.handleClick.bind(this)}。
- 由于 this.refs.[refName] 属性获取的是真实 DOM ,所以必须等到虚拟 DOM 插入文档以后,才能使用这个属性,否则会报错。上面代码中,通过为组件指定 Click 事件的回调函数,确保了只有等到真实 DOM 发生 Click 事件之后,才会读取 this.refs.[refName] 属性。
React之this.state以及点击事件
React中的state就相当于vue里的data数据存储,而小程序的this.setData就和React的this.setState类似。
//demo-4.js
import React from 'react';
class Demo4 extends React.Component{
// constructor(props){
// super(props)
// this.state = {
// liked:false
// }
// }
state = {
liked: false
}
handleClick(){
this.setState({liked: !this.state.liked});
}
render(){
let text = this.state.liked ? '喜欢' : '不喜欢';
return(
<div>
<p onClick={this.handleClick.bind(this)}>
你 {text} 这个点击.
</p>
</div>
)
}
}
export default Demo4;
上面代码是一个 LikeButton 组件,它的 constructor 方法(或者直接state声明)用于定义初始状态,也就是一个对象,这个对象可以通过 this.state 属性读取。当用户点击组件,导致状态变化,this.setState 方法就修改状态值,每次修改以后,自动调用 this.render 方法,再次渲染组件。
由于 this.props 和 this.state 都用于描述组件的特性,可能会产生混淆。一个简单的区分方法是,this.props 表示那些一旦定义,就不再改变的特性,而 this.state 是会随着用户互动而产生变化的特性。
利用input的onChange事件实现简单的双向绑定
vue里面v-model一键实现的事情React没有,我们可以利用input组件的onChange事件来简单实现它,直接上代码。
//Demo5.js
import React from 'react';
class Demo5 extends React.Component{
// constructor(props){
// super(props)
// this.state = {
// text:''
// }
// }
state = {
text: ''
}
handleChange(e){
this.setState(
{text: e.target.value}
);
}
render(){
return(
<div>
<p>
你输入了{this.state.text}
</p>
<input type="text" placeholder="请输入..." onChange={this.handleChange.bind(this)}/>
</div>
)
}
}
export default Demo5;
组件的生命周期
一个React组件的生命周期分为三个部分:挂载期(Mounting)、存在更新期(Updating)和销毁时(Unmounting)。
Mounting:已插入真实 DOM
当一个组件实例被创建并且插入到DOM中,以下钩子将被调用 constructor()
继承react的props,和设置state的初始化。
constructor(props) {
super(props); //不能缺少
this.state = {
color: props.initialColor
};
}
- Updating:当组件的props和state发生改变时,重新渲染页面是调用的。
- Unmounting:组件从DOM中移动时调用的。
React 为每个状态都提供了两种处理函数,will 函数在进入状态之前调用,
did 函数在进入状态之后调用,三种状态共计五种处理函数。
- componentWillMount()
- componentDidMount()
- componentWillUpdate(object nextProps, object nextState)
- componentDidUpdate(object prevProps, object prevState)
- componentWillUnmount()
事件
- onClick-点击事件
- onSubmit-表单提交事件,可以配合antdesign的表单校验
- onChange-input框数据变化,可实现数据的双向绑定(react里没有vue的v-modal)
style jsx
我们可以通过普通样式(className)和行内样式(LineStyle)控制React组件的样式:
- 使用className设置样式(与CSS的选择器相同)
import React from 'react';
// import styles from '../../static/css/demo.css'
class Demo6 extends React.Component{
// constructor(props){
// super(props)
// this.state = {
// text:''
// }
// }
state = {
text: ''
}
handleChange(e){
this.setState(
{text: e.target.value}
);
}
render(){
return(
<div>
<p className="style">
你输入了{this.state.text}
</p>
<input className="input" type="text" placeholder="请输入..." onChange={this.handleChange.bind(this)}/>
<style jsx>
{
` .style {
background: #dcdcdc;
font-size: 20px;
height:40px;
line-height:40px;
padding:0 2%;
}
.input{
width:96%;
height:40px;
padding:0 2%;
}
`
}
</style>
</div>
)
}
}
export default Demo6;
为什么要用next
Next.js是一个基于React的一个服务端渲染简约框架。它使用React语法,可以很好的实现代码的模块化,有利于代码的开发和维护。
默认服务端渲染模式,以文件系统为基础的客户端路由,类似nuxt.js;
与nuxt.js相同,pages文件目录即路由;
以webpack的热替换为基础的开发环境(npm run dev);
next创建的项目目录结构
next路由
- 默认从 pages 目录下取页面进行渲染返回给前端展示,路由可以使用Link(无刷新页面)或者a标签(刷新页面)
//pages/router.js
import Link from 'next/link';
<Link prefetch href='/about?id=11'>
<a >Link</a>
</Link>
<a href="/about?id=11">Link</a>
- 接收参数:getInitialProps方法context参数或者{props.query.id},同时在getInitialProps 里面获取数据,服务端渲染会提前执行这个方法获取数据渲染到模板,这里用到axios库,前后端都可以使用
//pages/test.js http://localhost:3000/test
import React from 'react';
import MyHeader from '../components/MyHeader';
import axios from '../lib/axios';
import { bmobConfigDevdev, bmobConfig } from '../lib/config';
import urls from '../lib/urls';
import { Table, Form, Icon, Input, Button } from 'antd';
const FormItem = Form.Item;
const config1 = {
headers: {
'X-Bmob-Application-Id': bmobConfig.applicationId,
'X-Bmob-REST-API-Key': bmobConfig.restApiKey,
'Content-Type': 'application/json'
}
};
const config2 = {
headers: {
'X-Bmob-Application-Id': bmobConfigDevdev.applicationId,
'X-Bmob-REST-API-Key': bmobConfigDevdev.restApiKey,
'Content-Type': 'application/json'
}
};
const myStyle = {
body: {
width: '1200px',
margin: '10px auto'
}
}
const columns = [{
title: 'id',
dataIndex: 'objectId',
key: 'objectId',
}, {
title: '姓名',
dataIndex: 'uname',
key: 'uname',
}, {
title: '电话',
dataIndex: 'uphone',
key: 'uphone',
}, {
title: '地址',
dataIndex: 'address',
key: 'address',
}];
const dataSource = [{
key: '1',
name: '胡彦斌',
age: 32,
address: '西湖区湖底公园1号'
}, {
key: '2',
name: '胡彦祖',
age: 42,
address: '西湖区湖底公园1号'
}];
//封装form组件
const MyForm = (props) => (
<Form layout="inline" onSubmit={this.handleSubmit}>
<FormItem>
<Button
type="primary"
htmlType="submit"
>
增加数据
</Button>
</FormItem>
</Form>
)
//声明类
class addDeleteSelectUpdate extends React.Component {
render() {
return (
<MyHeader title="用户列表">
<div style={myStyle.body}>
<p className="about">传递的参数为:{this.props.query}</p>
<p className="about">增加数据</p>
<MyForm ></MyForm>
<p className="about">获取数据{this.props.dataToString}</p>
<Table dataSource={this.props.data} columns={columns} size="small" />
<div className="">
<p className="about">遍历</p>
<ul>
{
this.props.data.map(els => {
return (
<li key={els.objectId}>
{els.address}
</li>
)
})
}
</ul>
</div>
<style jsx>
{`
.about {color:#666;padding:10px}
`}
</style>
</div>
</MyHeader>
)
}
}
/**
* @description 获取初始数据
*/
const isServer = typeof window === 'undefined'
addDeleteSelectUpdate.getInitialProps = async function (context) {
console.log(context.query);
const res = await axios.get(urls.userList, config1)
console.log(res.data);
var query = context.query;
return {
data: res.data.results,
dataToString: JSON.stringify(res.data.results),
query: JSON.stringify(context.query)
}
}
export default addDeleteSelectUpdate;
数据请求-axios配置
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
axios具有以下几个特点:
- 既可以在服务端使用又可以在浏览器端使用;
- 支持 Promise API(async await,解决地狱回调);
- 拦截请求和响应(401跳转登录等操作);
- 转换请求数据和响应数据;
- 自动转换 JSON 数据。
以下为axios配置代码:
//lib/axios.js
import axios from 'axios';
import NProgress from 'nprogress';
axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded';
axios.defaults.headers['X-Requested-With'] = 'XMLHttpRequest';
axios.defaults.withCredentials = true;
axios.defaults.timeout = 5000;
//请求之前
axios.interceptors.request.use((config)=>{
if(process.broswer) NProgress.start()//加一个loading
return config;
});
//
axios.interceptors.response.use(
response => {
// console.log('-------axiosResponse---------')
if(process.broswer) NProgress.done()
return response;
},
error => {
// console.log('-------axiosError---------:');
// console.log(error.response.status==401)
if(process.broswer) NProgress.done()
if(error.response&&error.response.status==401){
window.location.href="/login";//登录失效跳转
}
return Promise.reject(error);
}
);
export default axios;
使用时在页面引入axios配置文件axios.js,具体的axios配置可以参考link-axios文档。
//pages/login.js
import axios from '../lib/axios';
...
axios.post(urls.login,qs.stringify(obj))
.then(res=>{
console.log(res);
Router.push('/contract/list');
})
.catch(error=>{
console.log(error);
})
...
数据请求-开发环境代理
前后端分离以后,前端最常见的一个问题就是跨域,之前的om项目基于nuxt可以配置proxy,在next.js里需要用到express和http-proxy-middleware在服务端(node)端做一层转发,代码配置如下:
//server.js
var express =require('express');
var next = require('next');
const devProxy = {
'/api': {
target: 'http://39.107.58.75:9092/',// 目标服务器 host
pathRewrite: {'^/api': '/'}, // 重写请求,比如我们源访问的是api/login,那么请求会被解析为/www/login
changeOrigin: true,// 默认false,是否需要改变原始主机头为目标URL
},
'/jt':{
target: 'https://api.dededemo.com/plus/weapp.php?action=index&domain=http://23jt.net&skin=weapp&ver=3.0&page=1',// 目标服务器 host
// pathRewrite: {'^/api': '/'}, // 重写请求,比如我们源访问的是api/login,那么请求会被解析为/www/login
changeOrigin: true,// 默认false,是否需要改变原始主机头为目标URL
}
}
const port = parseInt(process.env.PORT, 10) || 3000
const env = process.env.NODE_ENV
const dev = env !== 'production'
const app = next({
dir: '.', // base directory where everything is, could move to src later
dev
})
const handle = app.getRequestHandler()
let server
app
.prepare()
.then(() => {
server = express()
// Set up the proxy.
if (dev && devProxy) {
const proxyMiddleware = require('http-proxy-middleware')
Object.keys(devProxy).forEach(function (context) {
server.use(proxyMiddleware(context, devProxy[context]))
})
}
// Default catch-all handler to allow Next.js to handle all other routes
server.all('*', (req, res) => {
req.headers['Content-Type'] = 'application/x-www-form-urlencoded';
req.headers['X-Requested-With'] = 'XMLHttpRequest';
handle(req, res);
// console.log(req.body);
}
)
server.listen(port, err => {
if (err) {
throw err
}
console.log(`> Ready on port ${port} [${env}]`)
})
})
.catch(err => {
console.log('An error occurred, unable to start the server')
console.log(err)
})
配置完server.js后还需要在packge.json中配置启动脚本:
"devdev": "cross-env NODE_ENV=development PORT=9527 node server.js",
注意上线后需要在ngnix里配置反向代理,就不存在跨域的问题。
在next.js里使用ant design
- babel配置
//.babelrc
{
"presets": [
"next/babel"
],
"plugins": [
[
"module-resolver", {
"root": ["."],
"alias": {
"styles": "./styles"
},
"cwd": "babelrc"
}],
["import", { "libraryName": "antd" }]
],
"ignore": []
}
- 引用ant design组件,需要用到ant design什么组件按照自己需要引用即可,各个组件使用方法参考ant design的link-官方文档
import {Form, Icon,Input, Button, Checkbox, message} from 'antd';
部署一个next.js项目
Next.js 项目的部署,需要一个 Node.js的服务器。
在开发环境服务器的入口文件就使用上文中提到的 server.js,在 server.js 里添加了针对部署环境的选择,代码如下
const dev = process.env.NODE_ENV !== 'production'
为了区分部署环境,我们需要在 package.json 中修改 script 属性如下:
"scripts": {
"dev": "next",
"devdev": "cross-env NODE_ENV=development PORT=9527 node server.js",
"build": "next build",
"start": "next start",
"generate": "next build && next export",
"export-h":"next export -h"
},
其中,build 命令是用于打包项目,start 命令是用于生产环境部署,devdev 命令是用于本地开发,PORT为启动端口。
注意的几个点
- 增加第三方npm包必须加上--save关键字,会pakage.json文件里加入依赖包;
//如增加js-md5库
$ npm install js-md5 --save
- 组件名称必须以大写字母开头,如MyHeader;
- React的事件中如果不用剪头函数,那就要用bind来绑定this,否则需要在constructor构造函数里调用this.handleClick = this.handleClick.bind(this),这里建议用onClick={this.handleClick.bind(this)}。
总结
只要熟悉React开发,上手一个Next项目很容易,Next 让前端项目开发效率更高。
参考资料
- ant design官方文档:https://ant.design/docs/react/introduce-cn
- ant design脚手架市场:http://scaffold.ant.design/#/?tags=antd&tags=react
- ant design使用小结:https://www.cnblogs.com/wx1993/p/6511389.html
- 用Next.js快速上手React服务器渲染 https://segmentfault.com/p/1210000010368182/read
- React 服务端渲染框架 Next.js 基于 Gank api 实战 https://orangexc.xyz/2017/10/19/Nextjs-gank/
- 基于 React 的通用框架 Next.js:服务端 React(axios)
https://www.zcfy.cc/article/react-universal-with-next-js-server-side-react-2158.html - 使用next.js完成从开发到部署 https://juejin.im/post/5b08078b51882538ad3f163d
- react学习线路图 https://juejin.im/entry/5b49af6d6fb9a04fe25ec224
- React使用Next.js作服务器端渲染 https://blog.csdn.net/xcg132566/article/details/78857017
- css modules 用法 http://www.ruanyifeng.com/blog/2016/06/css_modules.html
更多
更多前端技术请访问我的博客:https://hurely.github.io
react基础学习和react服务端渲染框架next.js踩坑的更多相关文章
- next.js、nuxt.js等服务端渲染框架构建的项目部署到服务器,并用PM2守护程序
前端渲染:vue.react等单页面项目应该这样子部署到服务器 貌似从前几年,前后端分离逐渐就开始流行起来,把一些渲染计算的工作抛向前端以便减轻服务端的压力,但为啥现在又开始流行在服务端渲染了呢?如v ...
- react服务端渲染框架
客户端渲染 加载一个空的html页面,然后请求一个打包的js文件,然后再客户端执行这个js文件 动态生成html内容然后插入到DOM元素上,在源代码查询中也只能看到空的html文档 没有任何其他内容 ...
- 优秀的vue服务端渲染框架
目前国内优秀的基于vue的ssr框架有minissr,可以在服务端生成html代码,有利于搜索引擎爬取. https://www.wechatmini.com/vue/minissr 使用方法可以参考 ...
- React服务端渲染总结
欢迎吐槽 : ) 本demo地址( 前端库React+mobx+ReactRouter ):https://github.com/Penggggg/react-ssr.本文为笔者自学总结,有错误的地方 ...
- 教你如何在React及Redux项目中进行服务端渲染
服务端渲染(SSR: Server Side Rendering)在React项目中有着广泛的应用场景 基于React虚拟DOM的特性,在浏览器端和服务端我们可以实现同构(可以使用同一份代码来实现多端 ...
- 从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 二十五║初探SSR服务端渲染(个人博客二)
缘起 时间真快,现在已经是这个系列教程的下半部 Vue 第 12 篇了,昨天我也简单思考了下,可能明天再来一篇,Vue 就基本告一段落了,因为什么呢,这里给大家说个题外话,当时写博文的时候,只是想给大 ...
- nuxt项目服务端渲染应用部署、使用pm2守护进程及遇到的问题处理
服务端渲染应用部署应该先编译构建,然后再启动 Nuxt 服务,可通过以下两个命令来完成: nuxt build nuxt start 我们已经在pakage.json里配置好script命令 { &q ...
- Vue.js 服务端渲染业务入门实践
作者:威威(沪江前端开发工程师) 本文原创,转载请注明作者及出处. 背景 最近, 产品同学一如往常笑嘻嘻的递来需求文档, 纵使内心万般拒绝, 身体倒是很诚实. 接过需求,好在需求不复杂, 简单构思 后 ...
- (十分钟视频教程)nodejs基础实战教程3:react服务端渲染入门篇
视频截图如下: (具体视频见文末) 前言: 这是小猫的第三篇node教程,本篇内容是由公众号粉丝票选得出的,相信大家对这篇教程是抱有较大希望的,这篇教程由小猫和一位多年的好朋友合作完成(笔名:谷雨,博 ...
随机推荐
- Win10提示“无法打开此计算机上的组策略对象”如何解决
为了更好地管理电脑,很多朋友都会去编辑Windows10的组策略.不过,有部分用户反馈自己在打开组策略的时候,遇到了“无法打开此计算机上的组策略对象”提示,无法打开组策略,这是怎么回事呢?下面,小编就 ...
- tensorflow到底难不难写
发信人: xhsoldier01 (风大人), 信区: ITExpress 标 题: Re: 没有GPU,tensorflow,AI公司都得死掉 发信站: 水木社区 (Thu Oct 10 20:25 ...
- [LeetCode] 288.Unique Word Abbreviation 独特的单词缩写
An abbreviation of a word follows the form <first letter><number><last letter>. Be ...
- 【嵌入式硬件Esp32】安装MQTT服务器(Windows) 并连接测试
对于不知道MQTT的肯定会问MQTT是干什么的....... 现在我有一个项目需求, 看到这个项目第一想法肯定需要一个服务器,所有的wifi设备和手机都去连接这个服务器,然后服务器进行信息的中转,类似 ...
- acme自动证书申请
安装acme.sh curl https://get.acme.sh | sh acme.sh默认安装到了当前家目录. [root@iZbp17hycbhnayg00ohec9Z ~]# ~/.acm ...
- Linux系统权限设置 - 运用指南
下面对linux系统下的有关权限操作命令进行了梳理总结,并配合简单实例进行说明.linux中除了常见的读(r).写(w).执行(x)权限以外,还有其他的一些特殊或隐藏权限,熟练掌握这些权限知识的使用, ...
- [转帖]FastDFS图片服务器单机安装步骤
FastDFS图片服务器单机安装步骤 https://www.cnblogs.com/yuesf/p/11847103.html 前面已经讲 一张图秒懂微服务的网络架构,通过此文章可以了解FastDF ...
- Oracle VM VirtualBox安装配置虚拟机Redhat7.6
首先,准备好材料,需要下载Oracle VM VirtualBox.Oracle19C的安装包.Redhat7.6镜像 下面列出地址: Oracle VM VirtualBox安装包:链接:https ...
- 使用Spring CROS解决项目中的跨域问题
CROS(Cross-Origin Resource Sharing) 用于解决浏览器中跨域请求的问题.简单的Get请求可以使用JSONP来解决,而对于其它复杂的请求则需要后端应用的支持CROS.Sp ...
- dubbo学习之路-SPI机制
dubbo学习之路-SPI机制 1.SPI 1.1Java SPI 原理 SPI是service provider interface简称.在java JDK中 内置的一种服务提供发现机制.它解决在一 ...