前言
这是我步入前端领域的第一站,也是我第一次在这个平台上面写文章。在简单了解 html、css、js 的相关语法和原理之后,花了差不多两个月的时间学习了 React 框架。在学 React 的最初,是想和前面一样跟着 MDN 的文档来学习的。然而,看了没多久就放弃了,因为看文档学习 React 对初学者实在是太不友好了。后来,我在哔哩哔哩上面找到一个 React 的学习视频。于是就跟着这个视频开始学习,一点点开始搭建 React 的知识框架,然后跟着视频做了一个简单的实时聊天的网站。至此,React 框架算是入门了吧。
写这篇文章的目的就是已这个简单的模板项目为依托总结一下这段时间学习React的成果以及自己的理解。
项目相关
项目介绍
该项目的名称叫做“硅谷直聘”,来源是尚硅谷React教程的示例项目。该项目是一个前后台分离的SPA(single page application,单页面应用),包含前端应用和后端应用。该项目的功能有:
- 用户注册和登录
- 两种用户类型:大神/老板的列表
- 用户信息的注册
- 实时聊天
技术选型
- 前端:React技术栈 + ES6 + Webpack
- 后端:Node + express + mongodb + socketIO
相关技术介绍
- 前端:
- React:前端的开发主要采用的是 React 技术栈,除了 React 本身,还包括 React技术栈相关的其他内容,主要有:用来进行状态管理的 Redux,进行路由管理的 react-router,用来进行页面美化的 antd-mobile
- ES6:全称ECMAScript 6,是JS家族中的一个版本。既然它是一种编程语言的规范,那么它的作用不言而喻。
- Webpack: JavaScript 应用程序的模块打包器,用来进行工程管理的。由于该项目的重点是在 React 上面,我并没有详细的了解过 Webpack 的具体内容,只是直接拿过来使用的。
- 后端:
- Node.js:我觉得,可以说 Node 的产生对于前端开发而言,是具有划时代意义的。在它产生之前,JS只能运行在浏览器端,作为 HTML 的附庸给 HTML 页面加上好看的动效。Node 的出现让 JS 从浏览器中剥离出来,使其可以跟 Java 一样运行在服务器端。
- express:一个Node服务端框架,用来搭建整个后端的服务器平台。
- mongodb:一种非关系型数据库,用来存储该应用所需要存储的数据。
- socketIO:给应用程序提供一个 socket 接口,用来实现实时数据传输,即实时聊天的功能
一点牢骚
可能是因为版本的问题,教程里给的项目根本跑不通,甚至里面很多东西都已经停止维护了。因此,跑通这个项目花了很多时间在踩坑上面。
React 工程相关
脚手架创建工程
- 全局安装 react 脚手架
npm install -g create-react-app
- 利用脚手架创建 react 工程并启动
create-react-app hello-react
cd hello-react
npm start
antd-mobile
- 将 antd-mobile 安装到项目中
npm install --save antd-mobile
- 实现组件按需打包
- 下载依赖包
npm install --save-dev react-app-rewired customize-cra
- 更改引用方式
/* package.json */ "scripts": { - "start": "react-scripts start", + "start": "react-app-rewired start", - "build": "react-scripts build", + "build": "react-app-rewired build", - "test": "react-scripts test --env=jsdom", + "test": "react-app-rewired test --env=jsdom", }- 在项目中根目录下创建config-overrides.js
module.exports = function override(config, env) { // do stuff with the webpack config... return config; };- 下载插件> npm install --save-dev babel-plugin-import
- 更改config-overrides.js
+ const { override, fixBabelImports } = require('customize-cra'); - module.exports = function override(config, env) { - // do stuff with the webpack config... - return config; - }; + module.exports = override( + fixBabelImports('import', { + libraryName: 'antd-mobile', + style: 'css', + }), + );- 更改引用方式
- import Button from 'antd-mobile/lib/button'; + import { Button } from 'antd-mobile'; - 实现定制主题
- 下载依赖包
npm install --save-dev less less-loader style-loader css-loader
- 创建 antd-theme.json文件,用来保存自定义的主题
{ "@brand-primary": "#1cae82", "@brand-primary-tap": "#1DA57A" }- 更改config-overrides.js中的代码
const { override, fixBabelImports, addLessLoader } = require('customize-cra'); const theme = require('./antd-theme.json'); module.exports = override( addLessLoader({ javascriptEnabled: true, modifyVars: theme, }), fixBabelImports('import', { libraryName: 'antd-mobile', libraryDirectory: 'es', style: true }) );- 编写 .less 文件定义用户自定义的样式,并且在入口 js 文件中引用。
react-router
react-router是用来切换当前显示界面的工具。根据不同的路径,在同一个页面渲染不同的组件。
- 安装
npm install --save react-router-dom
- 使用
- 在入口js中引入HashRouter,在页面渲染时,将所有的路由组件用HashRouter封装起来
- 将 Switch、Route 引入到需要进行路由切换的父组件中
- 在父组件中引入子组件
- 将每个子组件进行包装:
<Route path="/routeName" component={component}\>- 使用 Switch 组件将所有的路由组件封装起来
<Switch> <Route path="/routeName" component={component}\> <Switch/>- Redirect 实现路由的重定向,即访问根路径是页面渲染的路由组件,只需要指定to即可
<Redirect to="pathname"/>
redux
redux 是 react 家族中进行状态管理的工具,通过redux对状态进行托管。react组件需要时从redux中将状态取出来,进行显示或者进行修改。当某些状态需要同时被多个组件使用,此时的状态维护特别复杂,因此需要将状态进行统一管理。
- 安装: 需要安装三个依赖包,redux(redux本身的依赖包)、react-redux(react的插件库,用来简化react中应用redux),redux-thunk(进行redux异步编程的依赖包,redux默认是不能进行异步编程的)以及一个用来调试的redux-devtools-extension
npm install --save redux react-redux redux-thunk
npm install --save-dev redux-devtools-extension
- 使用:
创建包含四个.js文件redux文件夹,用来存放redux相关的代码
- store.js 这个文件里面的代码是固定的,如下:
import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import {composeWithDevTools} from 'redux-devtools-extension'
import reducers from './reducers'
export default createStore(
reducers,
composeWithDevTools(applyMiddleware(thunk))
)
- reducer.js 在这个文件中存放的是对项目中的公用状态,并且根据action和previous state进行存放的状态进行更新。然后用combineReducers将该文件管理的所有状态进行合并导出。代码如下:
import {combineReducers} from 'redux'
const initXXX = {
}
function xxx(state=initXXX, action) {
switch(action.state) {
default:
return state
}
}
export default combineReducers({
xxx
})
- action.js 这个文件中存放的是触发状态改变的action,表明什么时候出发状态的改变。这些 action 分为同步 action 和异步 action。
- action-type.js 这个文件中存放的是所有的action类型。
- 引入路由和Redux之后的 index.js 文件,组件渲染部分:
ReactDOM.render((
<Provider store={store}>
<HashRouter>
<Switch>
<Route path="/register" component={Register}/>
<Route path="/login" component={Login}/>
<Route component={Main}/>
</Switch>
</HashRouter>
</Provider>
), document.getElementById('root'))
跟 redux 进行交互的组件, 需要提前将他们包装成 container 容器组件:
import React, { Component } from 'react'
import {connect} from 'react-redux'
class conCom extends Component {
render() {
return (
<div>
react 容器组件
</div>
)
}
}
export default connect(
// 该组件所用到的 redux 管理的状态
state => (),
{
// 该组件所需要的 action
}
)(conCom)
包装完成之后,需要的状态和需要分发的action直接在props中寻找即可。
- Redux 工作流程
如图所示,redux 中保存的状态都保存在Store中。当想要改变状态的时候,需要dispatch 一个action,然后由 Reducers 根据之前的状态和 action 来进行状态的更改,action 可以直接由 React 组件进行调用。
跨域代理问题
关于跨域的代理问题,教程里面讲的是直接在 packege.json 文件中添加:
"proxy":"http://localhost:4000" // 在文件的最后添加跨域代理
然而,由于版本改变的问题,这样做的话会在跨域代理的时候,报错。最终的解决方案应该是添加安装http-proxy-middleware插件,然后在文件根目录下添加setupProxy.js文件,配置相关的跨域代理:
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
// ...
app.use(
'/test',
createProxyMiddleware({
target: 'http://localhost:8000',
changeOrigin: true,
})
);
}
后端工程相关
后端的开发整个架构就是 express+mongodb+socketIO。跟着官方文档来写,大部分问题不是很大。遇到比较多问题的就是socketIO。socketIO需要在客户端和服务端两端下载依赖包。两边的命令均为
npm install --save socket.io
- 服务端:
- 在 bin/www 文件中添加
var http = require('http'); var server = http.createServer(app); server.listen(port); server.on('error', onError); server.on('listening', onListening); require('../socketIO/server_socket')(server)- 创建 server_socket.js 文件,用来保存和socket相关的代码:
module.exports = function (server) { const io = require('socket.io')(server ,{ cors: { origin: 'http://localhost:3000', methords: ['GET', 'POST'] } }) // 监视客户端与服务器的连接 io.on('connection', function (socket) { console.log('有一个客户端连接上了服务器') // 绑定监听, 接收客户端发送的消息 socket.on('sendMsg', function (data) { console.log('服务器接收到客户端发送的消息', data) // 处理数据 data.name = data.name.toUpperCase() // 服务器向客户端发送消息 // socket.emit('receiveMsg', data) io.emit('receiveMsg', data) console.log('服务器向客户端发送消息', data) }) }) } - 客户端:废话不多说,直接贴代码
import io from 'socket.io-client'
// 连接服务器, 得到与服务器的连接对象
const socket = io('ws://localhost:8000')
// 绑定监听, 接收服务器发送的消息
socket.on('receiveMsg', function (data) {
console.log('客户端接收服务器发送的消息', data)
})
// 发送消息
socket.emit('sendMsg', {name: 'abc'})
console.log('客户端向服务器发消息', {name: 'abc'})
数据处理链路
在一个实际的应用当中,最关键的是数据如何流通。在这个工程中,数据流通的路径如图所示
react 组件在首屏渲染结束之后,向服务端发送 Ajax 请求,服务端收到请求之后,通过mongoose从数据库中查询相应的数据,并且向客户端返回查询到的数据。当客户端接收到从服务端返回的数据之后,交给 redux 中的 store 管理起来。
由于需要跟 redux 进行交互的组件已经包装成容器组件,只需要在props中寻找特定的参数即可进行状态的查询和action的分发。一个 action 对应一次 redux 状态的改变。调用action发生在react组件中,而改变后的状态则会直接同步到容器组件中,对应显示数据的改变。
写在后面的话
踩了两个月的坑,终于把demo项目做完了。这个项目收获还挺多,最重要的是走通了react设计应用的流程。虽然走通了流程,但也只是一个简单的入门而已。根本算不上学会。前路漫漫,与大家共勉。