想要搭建react首先要安装node和npm.
1,建立一个名字为react的文件夹
2,在此文件夹下 输入命令
npm init
生成package.json文件,此时的package.json中只有基础配置,想要项目运行还需要配置多种开发包
这是我上一个项目所用到的各种依赖,可以参考
{
"name": "text",
"version": "1.0.0",
"description": "测试react",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "node server.js",
"hot": "node server.hot.js",
"dist": "webpack --config webpack.config.dist.js --progress --colors --watch -p"
},
"author": "",
"license": "ISC",
"dependencies": {
"antd": "^2.13.11",
"axios": "^0.17.1",
"console-polyfill": "^0.3.0",
"core-js": "^2.5.1",
"es5-shim": "^4.5.9",
"es6-promise": "^4.1.1",
"fetch-ie8": "^1.5.0",
"highcharts": "^6.0.4",
"jquery": "^3.3.1",
"moment": "^2.20.1",
"msr": "^1.3.4",
"react": "^15.6.2",
"react-addons-css-transition-group": "^15.6.2",
"react-bmap": "^1.0.69",
"react-dom": "^15.6.2",
"react-highcharts": "^16.0.1",
"react-redux": "^4.4.5",
"react-router": "^2.8.1",
"redux": "^3.6.0",
"redux-thunk": "^2.1.0",
"video.js": "^6.7.3",
"videojs-contrib-hls": "^5.14.0",
"videojs-flash": "^2.1.0",
"videojs-thumbnails": "^1.0.3"
},
"devDependencies": {
"autoprefixer-loader": "^3.2.0",
"babel-core": "^6.18.2",
"babel-helpers": "^6.24.1",
"babel-loader": "^6.2.8",
"babel-plugin-import": "^1.6.2",
"babel-plugin-transform-class-properties": "^6.23.0",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.6.1",
"babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.5.0",
"babel-preset-react-hmre": "^1.1.1",
"babel-preset-stage-0": "^6.16.0",
"body-parser": "^1.15.1",
"clean-webpack-plugin": "^0.1.18",
"css-loader": "^0.23.1",
"express": "^4.14.0",
"extract-text-webpack-plugin": "^1.0.1",
"file-loader": "^0.8.5",
"html-webpack-plugin": "^2.22.0",
"http-proxy-middleware": "^0.17.3",
"json-loader": "^0.5.7",
"jsx-loader": "^0.13.2",
"less": "^2.6.1",
"less-loader": "^2.2.3",
"node-sass": "^3.11.3",
"react-hot-loader": "^1.3.1",
"react-transform-catch-errors": "^1.0.2",
"react-transform-hmr": "^1.0.4",
"redbox-react": "^1.3.3",
"redux-devtools": "^3.4.1",
"sass": "^0.5.0",
"sass-loader": "^4.0.2",
"style-loader": "^0.13.1",
"url-loader": "^0.5.7",
"webpack": "^1.13.0",
"webpack-dev-middleware": "^1.8.4",
"webpack-dev-server": "1.14.1",
"webpack-hot-middleware": "^2.20.0"
}
}
其中的script是命令优化
正常情况下 执行打包命令会输入
webpack --config webpack.config.dist.js
使用命令优化 就可以直接输入
npm run dist
代替
此时仅仅将各种依赖命令给出 并没有下载,若想项目运行 需要先下载这些包,执行命令
npm install
到现在为止 仅仅将项目的依赖安装完成,
package.json文件包含了react项目中使用的大多数依赖,但是对于你的项目还需要具体问题具体分析
下载完成后会自动生成node_modules文件夹,里面为我们所需的依赖
3 在文件夹下新建各种文件
这些都是一个react项目所需的基本文件
src中包含页面 路由 模板和jsx
.babelrc是babel文件,babel是用最新标准编写的js代码向下编译成随处可用的版本.
通俗的将 就是通过babel将我们编译的ES6,ES7等代码转为ES5 ,转换编译
.babelrc
{
"presets": [
"stage-0",
"es2015",
"react"
],
"plugins": [
"transform-decorators-legacy",
"transform-class-properties",
["import", {
"libraryName": "antd",
"style": "css"
}]
]
}
在根目录下建index.html文件 添加html标签<div id="app"></div>
index.html文件
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=EDGE" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="format-detection" content="telephone=no">
<meta name="full-screen" content="yes">
<meta name="x5-fullscreen" content="true">
<title>测试页面</title>
<link href="/dist/app.css?28dc475d0ddb7ffb1324" rel="stylesheet"></head>
<body>
<div id="root" style="position: relative;height: 100%;min-width: 1024px;"></div>
<script>
// 全局对象
var babelHelpers = {
typeof: function () {
return "undefined";
}
};
</script>
<script type="text/javascript" src="/project/dist/app.js?28dc475d0ddb7ffb1324"></script></body>
</html>
server.hot.js文件是热替换时的配置 在其中引入了webpack.config.hot.js文件
server.hot.js如下:
var webpack = require('webpack');
var express = require('express');
var config = require('./webpack.config.hot');
var proxyMiddleware = require('http-proxy-middleware')
var app = express();
var compiler = webpack(config);
app.use(require('webpack-dev-middleware')(compiler, {
publicPath: config.output.publicPath,
hot: true,
historyApiFallback: true,
inline: true,
progress: true,
stats: {
colors: true,
}
}));
app.use(require('webpack-hot-middleware')(compiler));
//将其他路由,全部返回index.html
app.get('*', function (req, res) {
res.sendFile(__dirname + '/index.html')
});
app.listen(8000, function () {
console.log('正常打开8000端口')
});
webpack.config.hot.js文件如下:
var babelPolyfill = require("babel-polyfill");
var path = require('path');
var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin'); //css单独打包
var HtmlWebpackPlugin = require('html-webpack-plugin'); //生成html
//定义地址
var ROOT_PATH = path.resolve(__dirname);
var APP_PATH = path.resolve(ROOT_PATH, 'src'); //__dirname 中的src目录,以此类推
var APP_FILE = path.resolve(APP_PATH, 'APP.jsx'); //根目录文件app.jsx地址
var BUILD_PATH = path.resolve(ROOT_PATH, '/project/dist'); //发布文件所存放的目录
module.exports = {
devtool: 'cheap-module-eval-source-map',
entry: {
app: [
'babel-polyfill',
'webpack-hot-middleware/client',
APP_FILE
]
},
output: {
publicPath: '/project/dist/', //编译好的文件,在服务器的路径,这是静态资源引用路径
path: BUILD_PATH, //发布文件地址
filename: '[name].js', //编译后的文件名字
chunkFilename: '[name].[chunkhash:5].min.js',
},
module: {
loaders: [{
test: /\.js$/,
exclude: /^node_modules$/,
loaders: ['react-hot', 'babel'],
include: [APP_PATH]
}, {
test: /\.css$/,
// exclude: /^node_modules$/,
loaders: ['style', 'css', 'autoprefixer'],
// include: [APP_PATH]
}, {
test: /\.less$/,
exclude: /^node_modules$/,
loaders: ['style', 'css', 'autoprefixer', 'less'],
include: [APP_PATH]
}, {
test: /\.scss$/,
exclude: /^node_modules$/,
loader: 'style-loader!css-loader!autoprefixer-loader!sass-loader',
include: [APP_PATH]
}, {
test: /\.(eot|woff|svg|ttf|woff2|gif|appcache|swf)(\?|$)/,
// exclude: /^node_modules$/,
loader: 'file-loader?name=[name].[ext]',
// include: [APP_PATH]
}, {
test: /\.(png|jpg|gif)$/,
exclude: /^node_modules$/,
loader: 'url-loader?limit=8192&name=images/[hash:8].[name].[ext]',
//注意后面那个limit的参数,当你图片大小小于这个限制的时候,会自动启用base64编码图片
include: [APP_PATH]
}, {
test: /\.jsx$/,
exclude: /^node_modules$/,
loaders: ['react-hot', 'jsx', 'babel'],
include: [APP_PATH]
}, {
test: /\.json$/,
loader: 'json-loader'
}]
},
plugins: [
new webpack.DefinePlugin({
//process.argv:当前进程的命令行参数数组。
//process.env:指向当前shell的环境变量,比如process.env.HOME。
'process.env': {
NODE_ENV: JSON.stringify('development') //定义编译环境
}
}),
new HtmlWebpackPlugin({ //根据模板插入css/js等生成最终HTML
filename: '../index.html', //生成的html存放路径,相对于 path
template: './src/template/index.html', //html模板路径
hash: false,
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin()
],
resolve: {
extensions: ['', '.js', '.jsx', '.less', '.scss', '.css'], //后缀名自动补全
alias: { // 文件路径别名设置
template: path.join(__dirname, 'src/template'),
// style: path.join(__dirname, 'src/style'),
router: path.join(__dirname, 'src/router'),
reduxFile: path.join(__dirname, 'src/redux'),
// images: path.join(__dirname, 'src/images'),
// plugin: path.join(__dirname, 'src/plugin'),
pages: path.join(__dirname, 'src/pages'),
// component: path.join(__dirname, 'src/component'),
}
}
};
此时配置已经基本完成
剩下src里面的
我在这里建立了3个页面 即pages里面的home,sign和title
先不管pages
4,先看redux
包含action,reducer和store
action是把数据从应用传递到store的有效荷载,是store数据的唯一来源
action中必须有一个字符串类型的type字段来表示要执行的动作.
以下为action的index.js
export const RECEIVE_MSG = 'RECEIVE_MSG';
export const receiveMsg = (userMsg) => ({
type: RECEIVE_MSG, userMsg
});
export const postMsg = () => {
return (dispatch) => {
var userMsg={
userName:"小明",
age:"18"
}
dispatch(receiveMsg(userMsg));
}
}
reducer为纯函数 接收旧的state和action返回新的state
也就是说 同样的输入,必定会得到同样的输出
纯函数是函数式编程,必须遵循一些约束
1不能改变参数
2不能调用系统的API
3不能调用不纯的方法,即每次会得到不同结果的方法,如Data.now(),
reducer的index.js文件如下
import * as actions from '../action/index';
export const userMsg=(state={
userName:'',
age:''
},action)=>{
switch (action.type){
case actions.RECEIVE_MSG:{
return Object.assign({},state,{
userName:action.userMsg.userName,
age:action.userMsg.age
})
}
default:{
return state
}
}
}
首先引入action文件夹下所有的action(此例中只有一个)用actions表示
定义一个函数,此函数接收一个RECERVE_MSG的行为,接收参数,返回新的state
store是保存数据的地方,可以看成一个容器,值得注意的是一个应用中只能有一个store,包含所有的数据
store中index.js代码如下
import {createStore,combineReducers,applyMiddleware,compose} from 'redux';
import * as reducer from '../reducer/index';
import thunk from 'redux-thunk';
var store;
if(!(window.__REDUX_DEVTOOLS_EXTENSION__ || window.__REDUX_DEVTOOLS_EXTENSION__)){
store = createStore(
combineReducers(reducer),
applyMiddleware(thunk)
);
}else{
store = createStore(
combineReducers(reducer),
compose(applyMiddleware(thunk),window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()) //插件调试,未安装会报错
);
}
export default store;
创建store,引入reducer 引入插件,判断浏览器是否安装了reduxDevTool,如果安转了则启用compose插件,未安装使用applyMiddleware,
至此,redux部分完成
5.pages 页面
我在这里建了3个页面home sign和title页面
首先看home页面
import React, { Component } from 'react';
import { hashHistory } from "react-router";
import { connect } from 'react-redux';
import {postMsg} from "reduxFile/action";
class Home extends React.Component {
constructor(props) {
super(props);
this.handleJumpLink=this.handleJumpLink.bind(this);
}
// 挂载
componentDidMount() {
}
// 接受数据
componentWillReceiveProps(nextProps) {
}
/**
* 跳转路由
* pathname state query
*/
handleJumpLink(pathname,state,query){
hashHistory.push({
pathname,state,query
})
}
getUserMsg(){
this.props.postMsg();
}
// 渲染
render() {
return (
<div>helloworld
<h1 onClick={e=>{this.handleJumpLink("/title",{},{})}}>这是标题</h1>
<h2 onClick={e=>{this.handleJumpLink("/sign",{},{})}}>这是签名</h2>
<div>姓名:{this.props.userMsg.userName}</div>
<div>年龄:{this.props.userMsg.age}</div>
<button onClick={e=>{this.getUserMsg.bind(this)()}}>获取用户信息</button>
</div>
);
}
}
// state 到 props 映射
const mapStateToProps = (state) => {
return {
userMsg:state.userMsg
}
}
//UI组件的参数到state.dispatch方法映射
const mapDispatchToProps = (dispatch, ownPorps) => {
return {
postMsg:()=>{
dispatch(postMsg());
},
}
}
// redux 与组件连接
const HomeComponent = connect(
mapStateToProps,
mapDispatchToProps
)(Home);
export default HomeComponent;
首先引入所需的各种依赖
然后引入我们之前写的action 即获取用户信息的 postMsg
新建home组件
render里面渲染页面元素
点击获取信息,发射dispatch获取到信息,并保存在props中
sign页面
import React, { Component } from 'react';
import { hashHistory } from "react-router";
import { connect } from 'react-redux';
class Sign extends React.Component{
constructor(props){
super(props);
}
render(){
return (
<div>这是签名页面</div>
)
}
}
// state 到 props 映射
const mapStateToProps = (state) => {
return {
}
}
//UI组件的参数到state.dispatch方法映射
const mapDispatchToProps = (dispatch, ownPorps) => {
return {
}
}
const SignComponent=connect(
mapStateToProps,
mapDispatchToProps
)(Sign);
export default SignComponent;
title页面
import React, { Component } from 'react';
import { hashHistory } from "react-router";
import { connect } from 'react-redux';
class Title extends React.Component {
constructor(props) {
super(props);
this.handleJumpLink=this.handleJumpLink.bind(this);
}
// 挂载
componentDidMount() {
}
// 接受数据
componentWillReceiveProps(nextProps) {
}
handleJumpLink(pathname,state,query){
hashHistory.push({
pathname,state,query
})
}
// 渲染
render() {
return (
<div>
这是标题页面
<h1 onClick={()=>{
this.handleJumpLink("/sign",{},{})
}}>去签名页面</h1>
</div>
);
}
}
// state 到 props 映射
const mapStateToProps = (state) => {
return {
}
}
//UI组件的参数到state.dispatch方法映射
const mapDispatchToProps = (dispatch, ownPorps) => {
return {
}
}
// redux 与组件连接
const TitleComponent = connect(
mapStateToProps,
mapDispatchToProps
)(Title);
export default TitleComponent;
6.页面建立好了 接下来就是关键的router了
先看代码
import React, { Component } from 'react';
import { Router, Route, IndexRoute, hashHistory } from 'react-router';
import { connect } from 'react-redux';
const history = hashHistory; // 路由方式
// root
class Roots extends Component{
constructor(props){
super(props);
}
render () {
return(
<div>
{this.props.children}
</div>
)
}
}
// state 到 props 映射
const mapStateToProps = (state) => {
return {
}
}
//UI组件的参数到state.dispatch方法映射
const mapDispatchToProps = (dispatch, ownPorps) => {
return {
}
}
// redux 与组件连接
const Root = connect(
mapStateToProps,
mapDispatchToProps,
)(Roots);
const home = (location, callback) => {
require.ensure([], require => {
callback(null, require('pages/home').default)
},'home')
};
// 标题页面
const title = (location, callback) => {
require.ensure([], require => {
callback(null, require('pages/title').default)
},'title')
}
// 签名页面
const sign = (location, callback) => {
require.ensure([], require => {
callback(null, require('pages/sign').default)
},'sign')
}
const RouteConfig = (
<Router history={history}>
<Route path='/' component={Root}>
<IndexRoute getComponent={home}></IndexRoute>
<Route path='home' getComponent={home}></Route>
<Route path='title' getComponent={title}></Route>
<Route path='sign' getComponent={sign}></Route>
</Route>
</Router>
);
export default RouteConfig;
先定义root组件 在引入各个页面
7.模板 template和app.jsp文件
先看template的index.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=EDGE" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="format-detection" content="telephone=no">
<meta name="full-screen" content="yes">
<meta name="x5-fullscreen" content="true">
<title>测试</title>
</head>
<body>
<div id="root" style="position: relative;height: 100%;min-width: 1024px;"></div>
<script>
// 全局对象
var babelHelpers = {
typeof: function () {
return "undefined";
}
};
</script>
</body>
</html>
这是app.jsp
require('console-polyfill');
require('es6-promise');
require('fetch-ie8');
require('core-js/fn/object/assign');
import React , { Component, PropTypes } from 'react';
import ReactDOM, { render } from 'react-dom';
import { Provider } from 'react-redux';
import Route from 'router/index.js';
import store from 'reduxFile/store/index';
import moment from 'moment';
import 'moment/locale/zh-cn';
moment.locale('zh-cn');
let rootEle = document.getElementById('root');
ReactDOM.render(
<Provider store={store}>
{Route}
</Provider>,
rootEle
);
到现在一个react项目已经建立完成了
在终端输入
npm run hot
运行成功 在浏览器输入localhost:8000 就可以看到我们的项目运行效果
点击获取信息 执行action行为
点击标题 进入标题页
总体来说 就是
1.package.json文件
2.安装各种依赖
3.babel文件
4 webpack文件
5 server文件
6 页面src
src里面包含 pages redux router template 和jsp文件