React
A JavaScript library for building user interfaces.
用于构建用户界面的 JavaScript 库
create-react-app
脚手架,用于构建项目的基本结构
$ npx create-react-app <proj-name>
# 或
$ yarn create react-app <proj-name>
在创建项目时,会自动创建出项目的基本结构,安装项目中使用到的依赖包资源,主要用到的依赖包:
- react:核心包
- react-dom:用于浏览器渲染
- react-scripts:包装了 webpack 相关的配置
npm scripts
{
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
}
- start:启动开发环境下的任务,会自动打开开发服务器 webpack-dev-server
- build:生产环境的任务,构建生产环境下的代码
- test:测试任务
- eject:弹出,将 webpack 的配置弹出到项目根目录下,这种弹出是不可撤销的
JSX
JSX 不是字符串内容,也不是 HTML 的结构,而是一个 JS 的表达式,可以将其理解为是一个 JavaScript 的语法扩展。
建议在 React 中配合使用 JSX,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。
在 JSX 的表达式中如果需要插入 JS 表达式内容,可使用 {}
语法结构:
const element = <h1>Hello { 'world' }</h1>
JSX 中使用 {}
插入 JS 表达式时,会进行转义处理,如果不希望转义而是需要渲染 html 文本,则可以使用 dangerouslySetInnerHTML
属性进行设置,该属性值是一个对象,对象中必须有 __html
属性绑定需要渲染的 html 文本:
<div dangerouslySetInnerHTML={
{__html: html}}></div>
原理
JSX 仅仅只是 React.createElement(component, props, ...children)
函数的语法糖,JSX 最终会经过 Babel 插件转译为 React.createElement()
的调用。
<div className="container">
<ul className="list">
<li>1</li>
<li className="active">2</li>
</ul>
<span>
<a href="/test.html">链接</a>
</span>
</div>
经过 Babel 转译:
React.createElement(
'div',
{
className: 'container' },
React.createElement(
'ul',
{
className: 'list' },
React.createElement(
'li',
null,
'1'
),
React.createElement(
'li',
{
className: 'active' },
'2'
)
),
React.createElement(
'span',
null,
React.createElement(
'a',
{
href: '/test.html' },
'链接'
)
)
)
函数调用后的执行结果:
{
type: 'div',
props: {
className: 'container'
},
children: [
{
type: 'ul',
props: {
className: 'list' },
children: [
{
type: 'li',
props: null,
children: ['1']
},
{
type: 'li',
props: {
className: 'active' },
children: ['2']
}
]
},
{
type: 'span',
props: null,
children: [
{
type: 'a',
props: {
href: '/test.html' },
children: ['链接']
}
]
}
]
}
虚拟 DOM 比较算法:
- diff
- fiber
组件
函数组件
function Element(props) {
return <h1>Hello { props.greeting }</h1>
}
- 组件名称采用帕斯卡命名规范(每个单词首字母都大写)
- 函数参数 props 是一个对象,表示的是组件在使用时接收到的属性
- 函数体内部使用
return
返回一个 JSX 所表示的 React 元素 - 在 JSX 表达式中可使用
<Element propName="propValue">
标签来使用组件结构
class 组件
// class 组件
class MyElement extends React.Component {
render() {
return <h1>Hello { this.props.greeting }</h1>
}
}
- 组件名称采用帕斯卡命名规范
- 继承自
React.Component
父类 - 必须实现
render()
方法,在方法体内部返回 React 元素 render()
方法体内部可使用this.props
获取组件接收到的属性。props
是从父类中继承过来的- 在 JSX 表达式中可使用
<MyElement propName="propValue">
标签来使用组件结构
React.Fragment
function TodoHeader() {
return (
<div>
<h1>主标题</h1>
<h2>副标题</h2>
</div>
)
}
无论是函数组件,还是 class 组件,在渲染元素节点时,都需要使用单个根元素节点将 UI 布局结构包裹起来。
可以使用 <React.Fragment>
组件将需要布局的结构包裹起来,这样在实际渲染的 DOM 树结构中就不会有多余的节点显示:
// 函数数组
function TodoHeader() {
return (
<React.Fragment>
<h1>主标题</h1>
<h2>副标题</h2>
</React.Fragment>
)
}
<React.Fragment></React.Fragment>
可简写为 <></>
组件通信
父子组件
- 父传子:使用 props 属性
- 子传父:利用 props 属性,在父组件中使用子组件时,传递父组件中一个函数引用作为属性值,在子组件中需要传递数据时,调用从属性中接收到的函数,在调用函数时传递所需要传输的数据作为参数。
跨组件层级
- 转换为父子组件通信
- Context
- reudx、mobx…
prop-types
运行时类型检测的工具,在定义组件时,使用该工具可以验证属性类型是否合法
$ npm i prop-types
// 运行时属性类型检测
static propTypes = {
todos: PropTypes.array
}
state
state 是组件内部私有要使用到的数据(类似于在 vue 组件中的 data)
修改 state 中的数据需要使用 setState()
方法来修改,页面才会响应式渲染
关于 setState()
的三件事:
- 不要直接修改 state,而是使用
setState()
来修改 setState()
的更新可能是异步的- state 的更新会被合并
表单
为单行文本框双向绑定数据(类似于 vue 中使用 v-model,但是 react 中没有这种指令):
绑定 value
属性、处理 change
事件,来实现数据的双向绑定
Ref
- 字符串类型的 ref(不推荐,已过时)
React.createRef()
:创建 ref 对象,关联组件或DOM节点,可使用该 ref 对象的current
属性获取组件实例或 DOM 对象
Context
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
Context 主要用于在全局范围内实现组件间的资源共享。
API
- React.createContext():创建 Context 对象
- Provider 组件:用于在 Context 对象上保存数据,有一个 value 属性
- Consumer 组件:消费在 Provider 组件上绑定的数据,Consumer 需要传递一个函数子元素
- Class.contextType:为类设置 contextType 静态(static)属性后,可以使用
this.context
获取最近 Context 上保存的数据
生命周期
class 组件中的生命周期钩子函数:
挂载阶段
- constructor():初始化:state、绑定事件处理程序中的 this
- render():渲染
- componentDidMount():在挂载结束后,可获取 DOM 树结构,可发送网络请求
componentWillMount()
:已过时,不推荐使用,如果确实需要使用,可使用 UNSAFE_componentWillMount()
更新阶段
- shouldComponentUpdate():通常用于优化,React.PureComponent
- render():渲染
- componentDidUpdate():更新后
卸载阶段
- componentWillUnmount():即将卸载
高阶组件(Higher Order Component)
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式(装饰者设计模式)。
高阶组件是参数为组件,返回值为新组件的函数。
Hook
在 react 16.8 中新增的功能。使得在函数组件中可以使用到类似 class 组件中的 state 等特性。
Hook 函数都是以 use
作为前缀开头,hook 函数只能用在函数组件或自定义 hook 的函数中。
state hook
useState()
effect hook
useEffect()
如果你熟悉 React class 的生命周期函数,你可以把
useEffect
Hook 看做componentDidMount
,componentDidUpdate
和componentWillUnmount
这三个函数的组合。
// componentDidMount()
useEffect(callback, [])
// componentDidUpdate()
// 当依赖项 item1, item2 发生改变时,会重新执行 callback 函数
useEffect(callback, [item1, item2])
useEffect(() => {
// statements
// TODO...
// 该返回的函数就相当于是 componentWillUnmount()
return () => {
}
}, [])
其它 hook
useCallback()
useMemo()
useRef()
Redux
是一个状态管理的库,本身与 React 没有任何关系
概念
- store:仓库
- state:状态,全局共享的状态数据
- reducer:纯函数,用于进行状态的同步更新。会接收 state 与 action 作为参数,在函数体内部根据 action 的 type 判断进行何种状态更新,返回更新后的新的状态对象
Reducer 必需符合以下规则:
- 仅使用 `state` 和 `action` 参数计算新的状态值
- 禁止直接修改 `state`。必须通过复制现有的 `state` 并对复制的值进行更改的方式来做 *不可变更新(immutable updates)*。
- 禁止任何异步逻辑、依赖随机值或导致其他“副作用”的代码
- dispatch:函数,用于触发调用 reducer() 更新状态。更新 state 的唯一方法是调用
store.dispatch()
并传入一个 action 对象。 - action:是一个普通对象,用于描述发生了什么事情,通常有 type、payload 属性
- action creator: action creator 是一个创建并返回一个 action 对象的函数。它的作用是让你不必每次都手动编写 action 对象
- …
Redux 期望所有状态更新都是使用不可变的方式。
使用
安装 redux
$ npm i redux
# 或
$ yarn add redux
创建 reducer
一个应用中可以有多种类别的状态需要进行管理,针对不同类别的状态编写各自独立的 reducer,如:
// counter
// 初始化状态数据
const initialState = {
count: 0
}
/**
* reducer 函数,用于同步更新状态数据
* @param {*} state 当前状态信息
* @param {*} action Action 对象,有 type 与 payload 属性
* @returns 更新后的状态对象
*/
const counterReducer = (state = initialState, {
type, payload }) => {
// 复制 state 状态数据
const copy = {
...state }
// 根据 action.type 判断,修改复制后的数据
switch (type) {
case 'INCREMENT': // 加
copy.count += 1
return copy
case 'DECREMENT': // 减
copy.count -= 1
return copy
default:
return state
}
}
export default counterReducer
将各独立的 reducer 合并为一个大的根 reducer:
import {
combineReducers } from 'redux'
import counterReducer from './counter'
import userReducer from './user'
// 将各独立的 reducer 合并为一个根 reducer
const rootReducer = combineReducers({
counter: counterReducer,
user: userReducer
})
export default rootReducer
创建 action creator 函数
action creator 主要用于创建 action 对象,以方便复用:
import {
DECREMENT, INCREMENT } from "./contants";
/**
* action creator,用于创建 action 对象
* @returns
*/
export const incrementAction = () => ({
type: INCREMENT
})
export const decrementAction = () => ({
type: DECREMENT
})
创建 store
import {
createStore } from 'redux'
import rootReducer from '../reducers'
// 创建 Store
const store = createStore(rootReducer)
// 导出
export default store
安装 react-redux 绑定库
redux 与 react 没有任何关系,如果需要在 react 中使用到 redux 的 store,则需要安装 react-redux 绑定库:
$ npm i react-redux
# 或
$ yarn add react-redux
利用 Provider 保存 store
利用 react-redux
中提供的 Provider 组件来保存 redux 中的 store,通常在应用的入口 index.js 文件中引入:
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import store from './store'
import App from './App'
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
在组件中使用 redux 的状态数据
使用 react-redux
中提供的 connect() 方法,在组件中连接 redux 的 store:
import {
connect } from 'react-redux'
import {
decrementAction, incrementAction } from './actions/counter'
class App extends React.Component {
// ...
}
// mapStateToProps 是一个函数,该函数传递 state 作为参数,
// 函数的返回值是一个对象,该返回对象中的属性会被合并到组件
// 的 props 中。
// 每当 store 中 state 状态被修改后,该方法都会被调用
const mapStateToProps = state => ({
count: state.counter.count
})
// const mapStateToProps = state => {
// return {
// count: state.counter.count
// }
// }
// mapDispatchToProps 是一个函数,该函数传递 dispatch 作为
// 参数,函数体内部返回一个对象,返回对象的属性也会被合并到
// 组件的 props 中
const mapDispatchToProps = dispatch => ({
increase() {
dispatch(incrementAction())
},
decrease() {
dispatch(decrementAction())
}
})
// connect() 调用后,返回的是一个 HOC
const hoc = connect(mapStateToProps, mapDispatchToProps)
export default hoc(App)
connect()
函数用于在组件中连接redux的store。
connect()
接收两个可选的参数:mapStateToProps
、mapDispatchToProps
connect()
的返回值是一个 HOC
(高阶组件),调用该 HOC 函数,将已有组件 (如:App) 作为参数传递,返回新组件。
HOC 高阶组件是利用 装饰器模式
对传递的参数组件进行功能增强:将 mapStateToProps()、mapDispatchToProps() 返回对象的属性合并到组件的 props 中
异步状态更新
可以使用 redux-thunk
这个中间件来实现异步状态更新
// TODO…
前端路由
在 SPA 单页面应用程序中,当需要实现界面切换效果(页面导航)时,通过 JavaScript 来完成在同一个页面中不同界面间的切换(DOM操作),这时就需要使用到前端路由
模式
- hash:在URL中使用
#
标识,当hash
值改变时(window.onhashchange)切换界面显示 - history:URL中没有显式的
#
,其 url 的格式与服务端路由的格式是完全一致的,因此,如果使用history
模式的路由,还需要服务端的配置支持。利用了 h5 中 history 新增的 API 实现其功能
不管使用 hash 模式还是 history 模式,在进行导航切换时,都不会向服务端发送请求
React Router
包资源:
- react-router:核心包
- **react-router-dom:**浏览器中渲染时使用到的路由包
- react-router-native:原生应用中使用到的路由包
- react-router-config:辅助用于进行静态路由配置时使用的包
安装
$ npm i react-router-dom
# 或
$ yarn add react-router-dom
组件API
- HashRouter: hash 模式路由
- BrowserRouter: history 模式路由
- Route: 配置路由-路由路径path、渲染组件component。可以有 component 与 render 属性来渲染组件,不要同时使用 component 与 render,当同时出现时,render 会被忽略
- Redirect: 重定向
- Link: 导航(链接)跳转
- Switch: 多分支选择(匹配 path 路径)
UI 组件库
安装
$ npm i antd
# 或
$ yarn add antd
引入 antd 组件与样式
import { Button } from 'atnd'
import 'antd/dist/antd.css'
高级配置 - craco
安装 craco
$ npm i @craco/craco -D
# 或
$ yarn add @craco/craco --dev
修改 package.json 中的 npm scripts
:
{
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test"
},
}
在项目根下创建 craco.config.js
文件:
module.exports = {
}
安装 craco-less 包:
$ npm i craco-less --save-dev
# 或
$ yarn add craco-less --dev
修改 craco.config.js
文件:
const CracoLessPlugin = require('craco-less')
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: {
// 自定义主题,修改 antd 使用到的 less 变量值
'@primary-color': '#1DA57A'
},
javascriptEnabled: true,
},
},
},
},
],
}
课堂案例
在线教育类产品后台管理系统
前端:
功能:
- 登录
- 首页-仪表盘
- 课程管理
- 课程列表
- 编辑课程
- 回收站
技术栈:
- create-react-app + react.js + react-router + redux + react-redux + redux-thunk
- axios
- antd
- echarts
- 富文本编辑器:wangeditor
- …
工作流程
-
分配工位与计算机,熟悉公司各种规章制度
-
配置开发环境,安装前端开发过程中使用到的工具,如 git、nodejs、vscode(webstorm)、小程序开发者工具、hbuilderx、PS…
开发流程:
- 本地没有源代码,下载源代码到本地
$ git clone <repo.url>
本地已有源代码,则直接更新到最新版本
$ git pull origin xxx
- 在工作空间中实现编码
- 在完成一个比较完整功能并自测通过后时,提交版本库
$ git status
$ git add -A
$ git commit -m 'flag: message'
- 推送到远程中央仓库
$ git push origin xxx
- 如果存在版本冲突问题,需要解决冲突
项目搭建流程:
-
利用脚手架(vue CLI 、create-react-app),搭建项目基本结构
-
安装项目基础依赖的包资源,如前端路由相关的包、状态管理相关的包、网络请求相关的包、UI组件库相关的包
- react-router-dom、redux、react-redux、redux-thunk、axios、antd、vant、element-ui
-
创建项目目录结构
project
|--.git # 隐藏目录,git 版本库管理
|--public # SPA 的 index.html
|--src # 项目源代码文件夹
|--|--assets # 静态资源,如图片、音频、视频等媒体资源文件
|--|--api # 与网络请求访问相关的代码,或 requests 目录名
|--|--components # 应用中需要复用的组件
|--|--actions # redux 的 action creator 代码
|--|--reducers # redux 的 reducer 代码
|--|--store # 状态管理库使用到的仓库代码
|--|--router # 路由相关的目录,或 routes 目录名
|--|--utils # 辅助工具代码
|--|--views # 页面组件代码,或 pages 目录名
|--|--styles # 外部样式表文件资源
|--|--scss # css 预处理器资源
|--|--App.vue / App.jsx / App.js # 应用的外层组件
|--|--main.js / index.js # 项目的入口JS文件
|--doc # 项目文档
|--test # 测试代码
|--package.json
|--README.md # 项目描述文件
|--.gitignore # git 在版本管理时需要忽略的资源
- 编写通用代码,如:axios 二次封装、redux中通用的 reducer、action creator、createStore() 等结构、路由相关通用代码
- 实现业务功能编码