H5入门01-React+dva+webpack+antd搭建框架
对于移动端开发者而言,开发H5一般使用 React +Dvajs + Webpack+andt.mobile进行开发,下面就用它们来搭建项目。
基础框架搭建
dva框架用于路由、架构、异步操作和网络请求。使用dva new
创建项目时,默认使用roadhog进行打包和开启服务。
webpack用于打包,使用webpack-dev-server来开启服务。相比roadhog,更加稳定,但是配置更加复杂。如果使用roadhog打包,就不需要安装webpack。
andt.mobile是阿里的ui框架,封装了很多常用的ui组件,功能强大,但是使用有点复杂。
注意:需要先安装npm和python。
参考文章:http://www.cnblogs.com/axel10/p/8973783.html
1 安装dva框架
截止 2017.1,最流行的社区 React 应用架构方案如下。
- 路由: React-Router
- 架构: Redux
- 异步操作: Redux-saga
缺点:要引入多个库,项目结构复杂。
dva = React-Router + Redux + Redux-saga, 是对上面三个 React 工具库的封装,还额外内置了 fetch,简化了 API,让开发 React 应用更加方便和快捷。
安装 dva-cli
通过 npm 安装 dva-cli 并确保版本是 0.9.1
或以上。
$ npm install dva-cli -g
$ dva -v
dva-cli version 0.9.1
创建新应用
安装完 dva-cli 之后,就可以在命令行里访问到 dva
命令。现在,你可以通过 dva new
创建新应用。
$ dva new dva-quickstart
这会创建 dva-quickstart
目录,包含项目初始化目录和文件,并提供开发服务器、构建脚本、数据 mock 服务、代理服务器等功能。
然后我们 cd
进入 dva-quickstart
目录,并启动开发服务器:
$ cd dva-quickstart
$ npm start
几秒钟后,你会看到以下输出:
Compiled successfully!
The app is running at:
http://localhost:8000/
Note that the development build is not optimized.
To create a production build, use npm run build.
在浏览器里打开 http://localhost:8000 ,你会看到 dva 的欢迎界面。
2 安装webpack
如果使用默认的roadhog打包,就跳过此步骤。
如果是webpack打包,就可以先删除roadhog的配置,再安装webpack。
- 删除.webpackrc
- 删除.roadhogrc.mock.js
- 删除roadhog:
npm uninstall --save-dev roadhog
基本配置
安装框架
//安装webpack框架
npm install --save-dev webpack webpack-cli
//安装webpack服务器,用于本地调试
npm install --save-dev webpack-dev-server
//安装本地打包框架
npm install --save-dev lodash
//安装css样式加载框架
npm install --save-dev style-loader css-loader
//安装scss样式加载框架
npm install --save-dev node-sass sass-loader
//安装文件加载框架
npm install --save-dev file-loader
//安装html处理框架
npm install --save-dev html-webpack-plugin
//安装文件清理框架
npm install --save-dev clean-webpack-plugin
//安装样式分离框架,用于将样式文件单独打包,提高加载速度。注意直接安装extract-text-webpack-plugin会发生兼容性问题。
npm install --save-dev extract-text-webpack-plugin@next
//安装webpack合并框架,用来合并webpack的配置文件
npm install --save-dev webpack-merge
//用来拷贝文件
npm install --save-dev copy-webpack-plugin
//用来压缩文件
npm install --save-dev uglifyjs-webpack-plugin
webpack.base.config.js
const path = require('path');
const webpack = require('webpack');
//配置参数
var config = {
//入口文件
entry: './src/index.js',
//出口文件
output: {
//name是入口文件名,默认为main,当存在多个入口文件是需要使用
filename: 'main.js',
//__dirname为系统变量,代表当前目录名
path: path.resolve(__dirname, 'dist'),
//指定所有资源的基础路径,默认为空,一般不需要指定
publicPath: '',
},
//用来追踪源代码,因为webpack打包源代码时,可能会很难追踪到错误和警告在源代码中的原始位置
devtool: 'inline-source-map',
//配置webpack服务器的参数
devServer: {
//热替换,当本地文件更新后,浏览器页面自动刷新
hot: true,
//设置代理
proxy: {
//对以'/open/api/weather/'开头的请求进行代理
'/open/api/weather/*': {
//需要代理的地址
target: 'https://www.sojson.com',
// 允许https请求
secure: true,
//允许跨域
changeOrigin: true
}
}
},
//加载模块
module: {
//加载规则
rules: [
{
//加载js文件
test: /\.jsx?$/,
//指定加载器
use: ['babel-loader'],
//指定需要加载的目录
include: path.join(process.cwd(), 'src')
},
{
test: /\.(png|svg|jpg|gif)$/,
use: ['file-loader?limit=8192&name=image/[name].[hash:4].[ext]'],
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: ['file-loader?limit=8192&name=image/[name].[hash:4].[ext]']
}
]
},
plugins: [
//热替换插件
new webpack.HotModuleReplacementPlugin(),
//定义全局变量,webpack编译后生效。_ENV_:将_ENV_定为全局变量,开发时会有警告。'window._ENV_':在window对象中定义_ENV_属性,开发时没警告。
new webpack.DefinePlugin({_ENV_: JSON.stringify({isMock: false}), 'window._ENV_': JSON.stringify({isMock: false})}),
]
}
module.exports = config;
package.json
{
...
"scripts": {
"start": "webpack-dev-server --hot --open --port 1111 --config webpack.dev.config.js",
"build": "webpack --config webpack.dev.config.js",
},
...
}
注意:如果使用roadhog打包,会自动识别webpack.config.js文件,并且要求webpack.config.js返回一个函数。
支持非ES5语法
安装框架:
//安装babel转码器
npm install --save-dev @babel/core
//用来转换ES6中新的API。babel默认只转换新的JS语法,比如Symbol、Promise等全局对象不会转码
npm install --save @babel/polyfill
//安装babel转码器的加载器
npm install --save-dev babel-loader
//用来支持react框架
npm install --save-dev @babel/preset-react
//用来支持ES6语法
npm install --save-dev @babel/preset-es2015
//用来支持最新的ES语法
npm install --save-dev @babel/preset-env
//用来支持已经正式提案,但是还没正式发版的ES语法。已废弃。
npm install --save-dev @babel/preset-stage-1
//用来支持Decorators语法。修饰器(Decorator)是ES7提案中的一个函数,用来修改类的行为。
npm install --save-dev @babel/plugin-proposal-decorators
//用来支持babel-runtime语法。@babel/runtime用于避免重复编译文件。
npm install --save-dev @babel/plugin-transform-runtime
//用于按需加载第三方库中的组件,而不是加载整个库
npm install --save-dev babel-plugin-import
//用于异步加载文件。在运行时加载,而不是编译时加载
npm install --save-dev @babel/plugin-syntax-dynamic-import
//用于在class中声明属性
cnpm install --save-dev babel-plugin-transform-class-properties
注意:babel7.0之前,使用babe-命名,7.0以后,使用@babel/命名。
.babelrc
{
//预设转码,用来将非ES6语法转换为ES6语法
"presets": [
"react",
"env",
"stage-1"
],
"plugins": ["transform-decorators-legacy" ,"transform-runtime"]
}
babel/polyfill的使用:
方法一:在头部js中:
require("@babel/polyfill")
或import "@babel/polyfill"
-
方法二:在webpack.config.js中添加:
module.exports = { entry: ['@babel/polyfill','./src/index.js'] };
开发配置
webpack.dev.config.js
const baseWebpackConfig = require('./webpack.base.config.js');
const merge = require('webpack-merge');
//开发环境配置参数
var config = {
//加载模式:开发模式或生产模式
mode: "development",
//加载模块
module: {
//加载规则
rules: [
{
test: /\.s?css$/,
//将样式文件打包到js中
use: ['style-loader', 'css-loader', 'sass-loader'],
},
]
},
};
module.exports = merge(baseWebpackConfig, config);
webpack把js文件打包,需要一个入口页来加载:index.html,默认位于项目的根目录。
CommonH5
生产配置
webpack.prod.config.js
const baseWebpackConfig = require('./webpack.base.config.js');
const merge = require('webpack-merge');
const path = require('path');
const webpack = require('webpack');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
//生产环境配置文件
var config = {
//指定生产环境
mode: "production",
//加载模块
module: {
//加载规则
rules: [
{
test: /\.s?css$/,
//将样式文件与js分离,提高加载速度
use: ExtractTextPlugin.extract({fallback: 'style-loader', use: ['css-loader', 'sass-loader']})
},
]
},
plugins: [
//压缩插件
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false
}
},
parallel: true
}),
//html加载插件
new HtmlWebpackPlugin({
filename: path.join(process.cwd(), 'dist/index.html'),
template: 'index-build.html',
inject: true,
hash: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),
//分离样式插件
new ExtractTextPlugin({filename: 'main.css'}),
// keep module.id stable when vendor modules does not change
new webpack.HashedModuleIdsPlugin(),
//用来复制文件
new CopyWebpackPlugin([]),
]
}
module.exports = merge(baseWebpackConfig, config);
生产打包时,需要在dist目录生成一个index.html文件,生成这个文件需要配置一个模板文件:index-duild.html
CommonH5
图片引用
使用webpack打包时,图片不能直接传路径。
//这样webpack打包时,会解析成http://localhost:1111/assets/yay.jpg,从而获取失败
有两种方式:
1.使用url函数:只能在.scss中使用
.float1 {
background: url(../../assets/yay.jpg) no-repeat center 0;
}
2.使用requrie函数:在.scss和.js中都能使用
3 安装antd-mobile
通过 npm 安装 antd-mobile
和 babel-plugin-import
。babel-plugin-import
是用来按需加载 antd 的脚本和样式的。
npm install --save antd-mobile
npm install --save-dev babel-plugin-import
如果使用roadhog打包,编辑 .webpackrc
,使 babel-plugin-import
插件生效。
{
"extraBabelPlugins": [
["import", { "libraryName": "antd-mobile", "libraryDirectory": "es", "style": "css" }]
]
}
如果使用webpack打包,编辑 .babelrc
,使 babel-plugin-import
插件生效。并且打包css文件时,不要排除node_modules,否则antd-mobile的css无法生效。
{
...
"plugins": [
["import", { "libraryName": "antd-mobile", "libraryDirectory": "es", "style": "css" }],
...
]
}
4 调试与打包
调试:npm start
打包:npm build。
打包后,会在dist目录生成所有的文件,包含一个index.html、一个main.css、n个图片等资源、n个js文件。
注意:使用dva构建项目默认生成的IndexPage.js,引用样式的方式在webpack中不支持,应改为:
import './IndexPage.scss';
...
第一个页面
1 简单页面
页面统一在src/routes目录进行开发,一个页面由一个js文件和一个scss(或css)文件组成。
首先写js文件。
Demo.js
import React from 'react';
import {connect} from 'dva'
import {queryDemoData} from '../services/serverDemo'
import {routerRedux} from 'dva/router';
import './Demo.scss'
//基础样式名
const baseStyle = 'demo';
//数据源
var dataSource = null;
/**
* Demo页面
*/
@connect(() => ({}))
export default class Demo extends React.Component {
//构造函数
constructor(props) {
super(props);
//使用state保存dataSource
this.state = {
dataSource: dataSource,
}
}
//界面渲染之前的回调函数
componentWillMount() {
// this.getTestData();
this.getRealData();
}
//界面渲染函数
render() {
let list = this.renderList();
return (
{list}
)
}
/**
* 获取测试数据
*/
getTestData() {
dataSource = [
{
img: 'https://zos.alipayobjects.com/rmsportal/dKbkpPXKfvZzWCM.png',
text: '6217000011112666'
},
{
img: 'https://zos.alipayobjects.com/rmsportal/XmwCzSeJiqpkuMB.png',
text: '6217000011112777'
}
];
this.setState({dataSource: dataSource});
}
/**
* 获取真实数据
*/
getRealData() {
queryDemoData().then((result)=> {
console.log(result);
//如果请求成功,就刷新页面
if (result.data.code == "000000") {
dataSource = result.data.data.list;
} else {
dataSource == null;
}
this.setState({dataSource: dataSource});
}, (error)=> {
console.log(error);
});
}
/**
* 生成列表
*/
renderList() {
let data = this.state.dataSource;
if (!data || !data.length) {
return null;
}
let items = [];
for (let i = 0; i < data.length; i++) {
let itemData = data[i];
let key = `ListItem${i}`;
let item =
{itemData.text}
;
items.push(item);
}
return (
{items}
);
}
/**
* 页面跳转
*/
onGo() {
//指定需要跳转的页面,页面路径为router.js中配置的path
let action = routerRedux.push('/');
//添加@connect后,redux会给页面添加dispatch属性,通过dispatch来分发action
this.props.dispatch(action);
}
}
注意:如果React.Component
写成React.component
或React.createComponent
,则需要添加constructor(props) {super(props);}
,否则webpack打包后会报错。
然后,写样式文件。创建样式时,统一以文件名作为所有样式的开头(通过&连接),这样可以避免样式被覆盖。
Demo.scss
.demo {
width: 100%;
height: 100%;
background-color: #F5F5F5;
overflow: auto;
//代表.css-demo-list.使用&连接上层样式名
&-list {
//布局方式
display: flex;
//flex方向.row代表水平排列,column代表垂直排列
flex-direction: column;
width: 100%;
height: 100%;
padding: 0px;
margin: 0px;
&-row {
display: flex;
flex-direction: column;
width: 100%;
height: 60px;
&-content {
display: flex;
flex-direction: row;
//flex方向的对齐
justify-content: flex-start;
//非flex方向的对齐:如果flex是水平的,那么内容垂直居中;如果flex是垂直的,那么内容水平居中;
align-items: center;
width: 100%;
height: 100%;
}
&-img {
width: 40px;
height: 40px;
margin-left: 10px;
}
&-text {
width: 100%;
margin-left: 10px;
font-size: 20px;
}
}
//分割线样式
&-separator {
width: 100%;
height: 1px;
background-color: #E5E5E5;
}
}
&-button {
width: 100%;
height: 60px;
margin: 10px;
font-size: 20px;
color: #00aaee;
background: #FFFFFF;
border: 1px solid #00aaee;
border-radius: 4px;
}
}
注意:css中属性名没有驼峰式,使用-分开,比如:在style中的borderTop在css中要写成border-top
最后,配置路由。页面完成后,需要在src/router.js中配置路由
import React from 'react';
import {Router, Route, Switch} from 'dva/router';
import IndexPage from './routes/IndexPage';
import Demo from './routes/Demo';
function RouterConfig({history}) {
return (
);
}
export default RouterConfig;
然后打开http://localhost:1111/#/demo,就可以访问页面了。
2 请求本地数据
dva构建项目时,帮我们封装了网路请求,可以方便的请求本地json数据和网络数据。
首先,创建本地数据。在mock目录下,创建json文件作为本地数据。
queryDemoData.json
{
"code": "000000",
"msg": "成功",
"data": {
"list": [
{
"img": "https://zos.alipayobjects.com/rmsportal/dKbkpPXKfvZzWCM.png",
"text": "6217000011112666"
},
{
"img": "https://zos.alipayobjects.com/rmsportal/XmwCzSeJiqpkuMB.png",
"text": "6217000011112777"
}
]
}
}
然后,创建请求方法。在src/services下创建请求方法。
demo.js
import request from '../utils/request';
export function queryDemoData() {
//获取本地数据
return request('/mock/queryDemoData.json');
}
然后,在页面调用请求方法。
//界面渲染之前的回调函数
componentWillMount() {
this.getRealData();
}
/**
* 获取真实数据
*/
getRealData() {
queryDemoData().then((result)=> {
console.log(result);
//如果请求成功,就刷新页面
if (result.data.code == "000000") {
dataSource = result.data.data.list;
} else {
dataSource == null;
}
this.setState({dataSource: dataSource});
}, (error)=> {
console.log(error);
});
}
3 请求网络数据
首先,设置代理。修改webpack.base.config.js的配置,在devServer中设置代理。
devServer: {
//热替换,当本地文件更新后,浏览器页面自动刷新
hot: true,
//设置代理
proxy: {
//对以'/open/api/weather/'开头的请求进行代理
'/open/api/weather/*': {
//需要代理的地址
target: 'https://www.sojson.com',
// 允许https请求
secure: true,
//允许跨域
changeOrigin: true
}
}
},
设置代理时,需要注意以下规则:
- proxy的属性代表代理url。请求的url必须以代理url开头,并且不包含
host
,代理才会生效;
- 代理url为
'*'
或'/'
代表代理所有请求,请求的url开头可以省略'/'
;
- 代理url必须以
'/'
开头('*'
除外),'/*'
或'/[url]/*'
在3.0版本不支持,在4.0版本支持;
然后,创建请求方法。在src/services下创建请求方法。
import request from '../utils/request';
export function queryDemoData() {
//_ENV_是通过webpack.DefinePlugin定义的系统变量,dev代表开发环境,prod代表生产环境.
if (_ENV_ == 'dev') {
//获取本地数据
return request('/mock/queryDemoData.json');
} else {
//获取网路数据.需要先在devServer中设置代理.
return request('/open/api/weather/json.shtml?city=北京');
}
}
然后,调用请求方法。同上。
4 页面跳转
首先,添加配置。
import {connect} from 'dva';
//用于生成页面跳转的action
import {routerRedux} from 'dva/router';
//添加@connect,参数可为空。用于给页面添加dispatch属性,
@connect()
export default class Demo extends React.Component
注意:connect还可以这样使用:export default connect()(IndexPage);
然后,跳转页面
/**
* 页面跳转
*/
onGo() {
//指定需要跳转的页面,页面路径为router.js中配置的path
let action = routerRedux.push('/');
//添加@connect后,redux会给页面添加dispatch属性,通过dispatch来分发action
this.props.dispatch(action);
}
5 数据传递
首先,创建model。model用来保存应用的数据,统一在src/models目录下配置。
modelDemo.js
export default {
//model的名字,同时也是他在全局state(应用数据,由多个model组成)上的属性
namespace: 'demo',
//当前model的初始值.页面之间是通过model来传递数据的
state: {demoData: '这是测试Demo',},
//创建同步修改器,根据action(指令)同步修改当前model的数据,返回修改后的数据
reducers: {
/**
* 同步修改器需要两个参数state和action,state代表当前modal的数据,action指定如何修改当前modal的数据
* action包含两个属性type和payload
* type为[model名]/[修改器名],比如:'demo/save'
* payload为对象,是action传递的数据
*/
save(state, action) {
return {...state, ...action.payload};
},
},
//创建异步修改器,根据action(指令)异步修改当前model的数据,返回修改后的数据
effects: {
/**
* 异步修改器使用Generator函数,需要两个参数action和effects,action与同步修改器的action一样,effects定义异步操作
* effects包含以下属性:
* call:用于调用异步逻辑,支持promise
* put:用来发送action(指令),参数为action
* select:参数为函数(state) => state.demo.demoData,用于从全局state中获取数据
*/
*fetch({payload}, {call, put, select}) {
yield call();
yield put({type: 'save'});
var data = yield select(state => state.demo.demoData);
},
},
};
然后,应用model。在src/index.js中添加model。
app.model(require('./models/modelDemo').default);
然后,发送数据。
onGo() {
//使用dispacth(action)修改model的数据
this.props.dispatch({
//指定model和修改器,格式为[namespace]+[函数名]
type: 'demo/save',
//传给修改器的数据
payload: {
demoData: '修改后的数据',
}
});
//指定需要跳转的页面,页面路径为router.js中配置的path
let action = routerRedux.push('/');
//添加@connect后,redux会给页面添加dispatch属性,通过dispatch来分发action
this.props.dispatch(action);
}
然后,接收数据。比如:在IndexPage.js中通过connect接收model中保存的数据。
/**
* 使用@connect(),并且传递一个函数为参数时,代表接收model中保存的数据
* 函数的参数state代表全局state,全局state保存了所有的model数据.
* 函数的返回值会被保存到当前页面的props中,通过this.props.来获取
*/
@connect((state) => ({demoData: state.demo.demoData}))
export default class IndexPage extends React.Component {
componentWillMount() {
//打印接收的model数据
console.log(this.props.demoData);
}
}
调试
1 添加断点
方法一:
在代码中,添加代码debugger;
,即在此处添加断点;
方法二:
在浏览器调试界面,选择Sources
-top
-webpack-internal://
-.
-src
目录下,找到对应的js文件,然后在js页面左侧,左键点击提交断点。
2 react-devtools调试
react-devtools相比chrome的调试工具,功能更强大。通过在浏览器中安装这个插件,可以查看组件的层次、各个组件的Props、States等信息。
下载地址:https://www.crx4chrome.com/down/62541/crx/
安装步骤:先下载.crx文件,然后打开更多工具
-扩展程序
,然后将.crx文件拖进去
3 Android调试
首先,在android上安装Stetho:https://reactnative.cn/docs/debugging/;
然后,在谷歌浏览器上打开chrome://inspect
;
注意:打开调试页面后,可以在地址输入栏打开自己的H5页面,这样就可以在自己的H5页面上调试Native的功能。
常见问题
1 npm查看模块版本
查看服务器上的模块版本:
- 查看所有的版本信息:
npm view [moduleName] versions
- 查看最新的版本信息:
npm view [moduleName] version
- 查看完整的版本信息:
npm info [moduleName]
查看本地的模块版本:
- 查看本地安装的版本:
npm ls [moduleName]
。注意,需要在模块的安装目录执行命令,比如:package.json所在的目录。
- 查看全局安装的版本:
npm ls [moduleName] -g
2 npm安装模块
2.1 指定版本安装模块:
方法一:
- 在
package.json
的dependencies中声明模块名和版本号,比如:"redux": "^4.0.0"
- 在
package.json
所在的目录下,运行命令:npm install
方法二:
使用@[版本号]
,比如:npm i --save [email protected]
;
2.2 自动安装合适版本的模块:
在package.json
所在的目录下,运行命令:npm install --save [moduleName]
如果要安装到开发环境,运行命令:npm install --save-dev [moduleName]
2.3 ~和^的作用和区别:
- 会匹配最新的小版本依赖包,比如1.2.3会匹配所有1.2.x版本,但是不包括1.3.0
- 会匹配最新的大版本依赖包,比如1.2.3会匹配所有1.x.x的包,包括1.3.0,但是不包括2.0.0
2.4 npm WARN处理
安装模块时,可能因为缺少依赖组件导致部分组件安装失败,比如:
npm WARN [email protected] requires a peer of eslint@^3.17.0 || ^4.0.0 but none was installed.
解决办法:
安装缺少的依赖组件,比如:npm install --save eslint
3 node-sass安装失败
出现Cannot download https://github.com/sass/node-sass/releases/download/版本号/XXX_binding.nod情况,是因为node-sass被墙了。
3.1 使用淘宝镜像源(推荐)
设置变量 sass_binary_site,指向淘宝镜像地址。示例:
npm i node-sass --sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
// 也可以设置系统环境变量的方式。示例
// linux、mac 下
SASS_BINARY_SITE=https://npm.taobao.org/mirrors/node-sass/ npm install node-sass
// window 下
set SASS_BINARY_SITE=https://npm.taobao.org/mirrors/node-sass/ && npm install node-sass
或者设置全局镜像源:
npm config set sass_binary_site https://npm.taobao.org/mirrors/node-sass/
3.2 使用cnpm安装
npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm i --save-dev node-sass
最后
代码:https://gitee.com/yanhuo2008/CommonH5