文章目录
一、React
网址:https://reactjs.org/
1.1、概述
React 起源于 Facebook(脸书) 的内部项目,它是一个用于构建用户界面的 javascript 库,Facebook用它来架设公司的Instagram网站,并于2013年5月开源。
React 拥有较高的性能,代码逻辑非常简单,越来越多的人已开始关注和使用它。认为它可能是将来 Web 开发的主流工具之一。
1.2、特点
◆ 声明式
你只需要描述UI看起来是什么样式,就跟写HTML一样,React负责渲染UI
◆ 基于组件
组件时React最重要的内容,组件表示页面中的部分内容 – react元素 – 虚拟dom
◆学习一次,随处使用
使用React可以开发Web应用(React-dom),使用React可以开发移动端(react-native),可以开发VR应用(react 360)
1.3、浏览器扩展与vscode开发扩展安装
Chrome 浏览器扩展:React Developer Tools
vscode安装react开发扩展:JS JSX Snippets
1.4、第一个react应用程序
react开发需要引入多个依赖文件,其中react.js、react-dom.js这两个文件是我们创建react应用程序必须要引入的依赖文件。
react.js 是核心,提供创建元素,组件等功能
https://unpkg.com/react@17/umd/react.development.js
react-dom.js 提供DOM相关功能
https://unpkg.com/react-dom@17/umd/react-dom.development.js
下载对应的react.js和react-dom.js的开发版本的js类库文件到本机中后,通过script引入到当前的网页中
// React 的核心库
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
//DOM 相关的功能
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
在html中定义reactjs渲染容器id和进行React实例化相关操作,完成helloworld显示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试一下</title>
</head>
<body>
<div id="app"></div>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script>
ReactDOM.render(React.createElement('div',{},'你好世界'),document.querySelector('#app'));
</script>
</body>
</html>
二、JSX
2.1、简介
由于通过React.createElement()方法创建的React元素有一些问题,代码比较繁琐,结构不直观,无法一眼看出描述的结构,不优雅,开发时写代码很不友好。
React使用 JSX 来替代常规的JavaScript,JSX 可以理解为的JavaScript语法扩展,它里面的标签申明要符合XML规范要求。React不一定非要使用JSX,但它有以下优点:
★ JSX 执行更快,因为它在编译为JavaScript代码后进行了优化
jsx是浏览不能够直接运行的,它需要一个转译器进行转译(babel),转为js
★ 它是类型安全的,在编译过程中就能发现错误
★ 声明式语法更加直观,与HTML结构相同,降低了学习成本,提升开发效率速
★ jsx语法中一定要有一个顶级元素包裹,否则编译报错,程序不能运行
js != jsx jsx => js+xml集合版本,js增强版,js有的它有,js没有它也有
2.2、重构helloworld
在项目中尝试 JSX 最快的方法是在页面中添加这个<script>
标签,引入解析jsx语法的babel类库,注意后续的script标签块中使用了jsx语法,则一定要申明类型 type="text/babel"
,否则babel将不进行解析jsx语法。
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
重构helloworld
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>jsx重构helloworld</title>
</head>
<body>
<div id="app"></div>
<script src="./js/babel.min.js"></script>
<script src="./js/react.development.js"></script>
<script src="./js/react-dom.development.js"></script>
<script type="text/babel">
ReactDOM.render(
<div>你好世界</div>,
document.getElementById('app');
);
</script>
</body>
</html>
2.3、在 jsx 中语法中的 js 表达式
2.3.1、嵌入JS表达式
在jsx语法中,要把JS代码写到{ }中,所有标签必须要闭合。
let num = 100
let bool = false;
// JSX 语法
var myh1 = (
<div>
{/* 我是注释 */}
{num}
<hr />
{/* 三目运算 */}
{true ? "条件为真" : "条件为假"}
</div>
)
2.3.2、属性绑定动态值
const title = '你好世界';
// 一定要会使用它,这样用法在工作中天天用
<button title={title}>按钮</button>
<style>
/* class 是es6定义类的关键词,在jsx中不能使用,只能通过 className来调用定义好的样式类 */
.btn{color:red;}
</style>
// classclassName一定要牢记,因为在工作,写样式就要用到
// for htmlFor
<button className="btn">按钮</button>
#jsx中绑定样式 style 只绑定对象
<div style={ {color: 'red'} }></div>
#动态显示html元素 dangerouslySetInnerHTML 可以进行对html标签进行反转义
#在react17中jsx可以直接解析html,但是对于转义后的html还不会自动转义,需要使用它
const html = 你好 ©世界
#不转义html元素输出 {两个下划线html:html}
<div dangerouslySetInnerHTML={ {__html:html} }></div>
样式的属性绑定
2.3.3、jsx渲染数组列表
<script type="text/babel">
let arr = ["张三","李四","王五","赵六"];
let app = document.getElementById('app');
ReactDOM.render(
<div>
{/* jsx中如果是一维数组,直接写上就可以遍历渲染了 */}
{ arr }
</div>,
app
);
</script>
2.3.4、jsx渲染数组列表并处理
<script type="text/babel">
let nameList = ['张三', '李四', '王五'];
ReactDOM.render(
<div>
{
// 方案1
nameList.map((item,index) => {
return (<h3>{item}</h3>)
});
// 方案2 【推荐】
nameList.map((item,index) => <h3>{item}</h3>);
</div>,
document.getElementById('app')
);
</script>
三、React脚手架
React团队主要推荐使用create-react-app来创建React新的单页应用项目的最佳方式。
React脚手架(create-react-app)意义
♥ 脚手架是官方推荐,零配置,无需手动配置繁琐的工具即可使用
♥ 充分利用 Webpack,Babel,ESLint等工具辅助项目开发
♥ 关注业务,而不是工具配置
create-react-app会配置你的开发环境,以便使你能够使用最新的avaScript特性,提供良好的开发体验,并为生产环境优化你的应用程序。你需要在你的机器上安装 Node >= 8.10 和 npm >= 5.6。
建议可以安装 js包管理工具 yarn (facebook开发的) — 速度会快一些
npm i -g yarn
3.1、创建React项目
npx create-react-app my-app // 创建项目,时间会有点的久
cd my-app // 进入到安装项目中
npm start // 启动项目
四、React 组件
4.1、简介
组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。从概念上类似于 JavaScript 类或函数。它接受任意的入参(props),并返回用于描述页面展示内容的 React元素(jsx)。 定义好的组件一定要有返回值
react中定义组件的方式2种
㉿ 函数组件(无状态组件/UI组件)
㉿ 类组件(状态组件/容器组件)
4.2、组件的创建方式
4.2.1、函数创建组件
函数组件(无状态组件):使用JS的函数创建组件
函数名称必须以大写字母开头,
函数组件必须有返回值(jsx),表示该组件的结构,且内容必须有顶级元素
import React from 'react'
// 函数名首字母必须大写
function Hello() {
return (
<div>这是第一个函数组件</div>
)
}
4.2.2、类组件
使用ES6语法的class创建的组件(状态组件)
类名称必须要大写字母开头
类组件要继承React.Component父类,从而可以使用父类中提供的方法或者属性
类组件必须提供 render 方法,用于页面结构渲染,结构必须要有顶级元素,且必须return去返回
import React from 'react'
// 创建class类,继承React.Component,在里面提供render方法,在return里面返回内容
class Hello extends React.Component{
render(){
return (
<div>这是第一个类组件</div>
)
}
}
4.3、父子组件传值(props 【只读属性】)
组件间传值,在React中是通过只读属性 props 来完成数据传递的。
props:接受任意的入参,并返回用于描述页面展示内容的 React元素。
props中的数据是不能被修改的,只能读取。
react中的数据不是双向,单向数据流。
4.3.1、函数组件
4.3.2、类组件
class Cmp extends React.Component{
render(){
return (
<div>{ this.props.name } -- 我是一个类</div>
);
}
}
五、React事件处理
5.1、事件绑定
React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:
☞React 事件的命名采用小驼峰式,而不是纯小写。
onClick onChange
☞使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
onClick={this.fn} // 绑定实现方法一定要用{函数或方法} // 注函数或方法不能用小括号
☞类组件与函数组件绑定事件是差不多的,只是在类组件中绑定事件函数的时候需要用到this,代表指向当前的类的引用,在函数中不需要调用this
export default class extends React.Component {
clickHandle(e){
console.log('点了')
}
render(){
return (
<div><button onClick = {this.clickHandle}>点我点我点我</button></div>
)
}
}
类组件
函数组件
5.2、事件传值
☆ 函数组件
☆ 类组件
5.3、事件对象
React中可以通过事件处理函数的参数获取到事件对象,它的事件对象叫做:合成事件,即兼容所有浏览器,无需担心跨浏览器兼容问题,此事件对象还拥有和浏览器原生事件相同的接口,包括 stopPropagation()
和 preventDefault()
,如果你想获取到原生事件对象,可以通过 e.nativeEvent 属性来进行获取。
作用:获取dom元素或数据传值
react中的实现没有提供像vue中的事件修饰符,像冒泡和默认行为,需要开发者自行解决。
export default class extends React.Component {
clickHandle(e){
// 获取原生事件对象
console.log(e.nativeEvent)
}
render(){
return (
<div><button onClick = {this.clickHandle}>点我点我点我</button></div>
)
}
}
类组件
如果使用柯里化函数绑定事件,event对象传递
5.4、this指向问题
在JSX事件函数方法中的 this,默认不会绑定 this指向。如果你忘记绑定,当你调用这方法的时候, this 的值为 undefined。所以使用时一定要绑定好this的指向。
♣ 构造方法中绑定
constructor(props){
super(props)
// 在构造方法中指定this指向 <button onClick={this.fun()}>按钮</button>
this.fun = this.fun.bind(this)
}
♣ 申明是使用bind绑定
<button onClick={this.fun.bind(this)}>按钮</button>
♣ 箭头函数绑定
<button onClick={(evt) => this.fun(evt)}>按钮</button>
♣ 定义事件方法使用箭头函数来绑定
// 通过箭头函数定义事件方法,也能解决this指向问题
fn = (evt) => {
alert(123);
}
六、State状态
state状态只在class类组件才有,函数组件没有此功能。
6.1、基本使用
◆ 状态(state)即数据,是组件内部的私有数据,只能在组件内部使用
◆ state的值是对象,表示一个组件中可以有多个数据
◆ 通过this.state来获取状态,react中没有做数据代理
◆ state数据值可以修改 this.setState
◆ state可以定义在类的构造方法中也可以写在类的成员属性中
export default class extends React.Component {
constructor(props){
super(props)
// 第一种初始化方式
this.state = {
count : 0
}
}
/*
// 第二种初始化方式
state = {
count:1
}
*/
render(){
return (
<div>计数器 :{this.state.count}</div>
)
}
}
6.2、修改状态
state中的值不能直接通过修改state中的值来进行修改数据操作,react提供一个this.setState
方法来完成state数据的修改操作
setState() 作用:
1.修改 state 字段的值
2.更新UI 视图
注:setState本身并不是异步,只是因为react的性能优化机制体现为异步。在react的生命周期函数或者作用域下为异步,在原生的环境下为同步。
总结:setState即是异步也是同步
语法1
this.setState({
key:value
})
语法2 推荐
this.setState(state => {
return {key:value}
})
##异步
state = {
number:1
};
componentDidMount(){
this.setState({number:3},()=>{
// 获取最新的修改数据
console.log(this.state.number)
})
}
##同步
// 在延时器中
state = {
number:1
};
componentDidMount(){
setTimeout(()=>{
this.setState({number:3})
console.log(this.state.number)
},0)
}
// 在原生事件中
state = {
number:1
};
componentDidMount() {
document.body.addEventListener('click', this.changeVal, false);
}
changeVal = () => {
this.setState({
number: 3
})
console.log(this.state.number)
}
6.3、props与state区别
❉ props 中存储的数据,都是外界传递到组件中的
❉ props 中的数据,都是只读的
❉ state 中的数据,都是可读可写的
❉ props 在函数声明或类申明的组件中都有
❉ state 只有类申明的组件中才有
七、props进阶
7.1、children属性
children属性,表示组件标签的子节点,当组件标签有子节点时,props就会有该属性,与与普通的props一样,其值可以使任意类型。单标签和双标签中没有数据都是没有此属性。
// 父组件
class App extends React.Component {
render() {
return (
<div>
<Cmp>我是children中的值</Cmp>
</div>
)
}
}
// 子组件
{props.children} //获取数据
7.2、类型限制(prop-types)
对于组件来说,props是外部传入的,无法保证组件使用者传入什么格式的数据,简单来说就是组件调用者可能不知道组件封装着需要什么样的数据,如果传入的数据不对,可能会导致程序异常,所以必须要对于props传入的数据类型进行校验。
❃ 安装校验包
npm i -S prop-types
# 在组件中导入
import PropTypes from 'prop-types'
# 函数组件
function App(){}
// 验证规则
App.propTypes = {
prop-name:PropTypes.string
}
# 类组件
class App extends Component{
// 类内部完成 检查
static propTypes = {
prop-name:PropTypes.string
}
}
- 类型: array、bool、func、number、object、string
- React元素类型:element
- 必填项:isRequired·‘’’
- 特定结构的对象: shape({})
☠ 类组件
☠ 函数组件
7.3、默认值(defaultProps)
如果props没有属性没有传过数据,为了不让程序异常,可以设置其默认值。
# 函数组件
function App(){}
App.defaultProps = {
title: '标题'
}
# 类组件
class App extends Component {
static defaultProps = {
title: '标题'
}
}
函数组件默认值
类组件默认值设置
八、表单处理
8.1、受控组件
将state与表单项中的value值绑定在一起,有state的值来控制表单元素的值,称为受控组件。
绑定步骤:
❃ 在state中添加一个状态,作为表单元素的value值
❃ 给表单元素绑定change事件,将表单元素的值设置为state的值
<input type="text" value={this.state.username} onChange={this.inputChange.bind(this)} />
//注:多表单元素需优化事件方法
this.setState({
[e.target.name]: e.target.value
})
8.2、非受控组件
没有和state数据源进行关联的表单项,而是借助ref,使用元素DOM方式获取表单元素值
使用步骤
❃ 调用 React.createRef() 方法创建ref对象
❃ 将创建好的 ref 对象添加到文本框中
❃ 通过ref对象获取到文本框的值
class App extends React.Component {
constructor(props){
super(props)
//创建 ref
this.username = React.createRef()
}
// 获取文本框的值
fn =() => {
console.log(this.username.current.value)
}
render(){
return (
<div>
<input type ="text" ref={this.username} />
<button onClick ={this.fn}>获取值</button>
</div>
)
}
}
九、生命周期
函数组件无生命周期,生命周期只有类组件才拥有。
生命周期函数指在某一时刻组件会自动调用并执行的函数。React每个类组件都包含生命周期方法,以便于在运行过程中特定的阶段执行这些方法。例如:我们希望在第一次将其呈现到DOM时设置一个计时器Clock。这在React中称为“安装”。我们也想在删除由产生的DOM时清除该计时器Clock。这在React中称为“卸载”。
参考:链接
完整的生命周期图
常用的生命周期图
☂ constructor(props)
React组件的构造函数在挂载之前被调用。在实现React.Component构造函数时,需要先在添加其它内容前,调用super(props),用来将父组件传来的props绑定到继承类中。
只调用一次
constructor(props) {
super(props)
}
☂ static getDerivedStateFromProps(nextProps, prevState)
此方法是react16.3之后新增,会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。
此方法适用于罕见的用例,即当前组件的 state 的值在任何时候都取决于 props传入。
state = {
num: 0
};
render() {
return <div>当前的num是{this.state.num}</div>;
}
// 从props中获取数据,绑定到当前的这个组件中的state
// nextProps 父组件传递过来的整个props对象
// prevState 当前组件中的状态对象state
static getDerivedStateFromProps(nextProps, prevState) {
// 不需要更新当前state
return null
}
☂ render()
render()方法是必需的,它主要负责组件的渲染,会被重复调用若干次
☂componentDidMount
它会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。
☂ shouldComponentUpdate(nextProps, nextState)
当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。返回值默认为 true则组件继续渲染,为false则当前组件不会渲染。首次渲染或使用 forceUpdate() 时不会调用该方法。此方法仅作为性能优化的方式而存在。你也可以考虑使用内置的 PureComponent 组件,而不是手动编写 shouldComponentUpdate()。PureComponent 会对 props 和 state 进行浅层比较,并减少了跳过必要更新的可能性。
当this.setState()修改了state中的数据后,当前组件将重新渲染,同时也会重新渲染子组件,但只会渲染当前组件子树(当前组件以其所有子组件)
shouldComponentUpdate(nextProps, nextState) {
// 判断是否需要被渲染,如果需要则返回true,否则返回false
if (nextProps.b === this.props.b) {
return false;
} else {
return true;
}
}
// 简化方法:只需要将类组件的继承关系改成继承`PureComponent`即可,这样一来,框架会自动判断值是否有变化进一步决定组件是否需要被渲染。
import React, { PureComponent } from "react";
class Cmp extends PureComponent {
render() {
console.log("Cmp被渲染了");
return <div>父亲传递过来的值b为:{this.props.b}</div>;
}
}
export default Cmp
☂ getSnapshotBeforeUpdate(prevProps, prevState)
在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息,此生命周期的任何返回值将作为参数传递给 componentDidUpdate()
☂ componentDidUpdate(prevProps, prevState, snapshot)
会在数据更新后会被立即调用。首次渲染不会执行此方法。
☂ componentWillUnmount()
会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作
☂ componentWillMount() / UNSAFE_componentWillMount()
已被弃用,在挂载之前被调用。它在 render() 之前调用,因此在此方法中同步调用 setState() 不会触发额外渲染
☂ componentWillReceiveProps()/UNSAFE_componentWillReceiveProps(nextProps)
已被弃用,它会在已挂载的组件接收新的 props 之前被调用。
☂ componentWillUpdate()/UNSAFE_componentWillUpdate(nextProps, nextState)
已被弃用,当组件收到新的 props 或 state 时,会在渲染之前调用此方法,初始渲染不会调用此方法。
十、组件传值
10.1、父组件与子组件传值
☠ 父组件将自己的状态传递给子组件,子组件当做属性来接收,当父组件更改自己状态的时候,子组件接收到的属性就会发生改变 props
☠ 父组件利用ref对子组件做标记,通过调用子组件的方法以更改子组件的状态,也可以调用子组件的方法(说明:ref获取当前组件对象,只针对的是使用类创建的组件才可以用此方案,类有实例,而函数没有实例)
注:ref只能给类组件去使用,得到的是组件实例,而函数组件没有实例的
ref的另类写法
10.2、子组件传值给父组件
父组件将自己的某个方法传递给子组件,在方法里可以做任意操作,比如可以更改状态,子组件通过this.props接收到父组件的方法后调用,来给父组件传值。
因为在react中props是可以传任意类型数据,而函数是不是也是任意类型数据之一
父组件 props传过来当前自身的函数体 子组件通过props得到 this.props.名称()执行
10.3、跨组件通信
在react没有类似vue中的事件总线来解决这个问题。在实际的项目中,当需要组件间跨级访问信息时,如果还使用组件层层传递props,此时代码显得不那么优雅,甚至有些冗余。在react中,我们还可以使用context来实现跨级父子组件间的通信。
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据。
在React的Context中,数据我们可当成商品,发布数据的组件会用provider身份(卖方),接收数据的组件使用consumer身份(卖方)
ღ 创建Context对象
当React渲染一个订阅了这个Context对象的组件,这个组件会从组件树中离自身最近的那个匹配的Provider中读取到当前的context值。
// 定义全局context,全局数据源
import { createContext } from "react"
export default createContext()
ღ 发布消息
在App.jsx组件中发布消息,这样所有的组件都可以消费它的消息。
import context from "./Context ";
let { Provider } = context;
class App extends Component {
state = {
count: 12345,
};
render() {
return (
<div>
<Provider value={this.state.count}>
<Cmp1></Cmp1>
<Cmp2></Cmp2>
</Provider>
</div>
);
}
}
export default App;
ღ 组件消费
在子组件中通过Api完成消费动作,从而实现消息通信。消费的方式有2种:
方式1:通过组件消费
import context from "../Context ";
let { Consumer } = context;
class Cmp1 extends Component {
render() {
return (
<div>
<Consumer>
{(value) => {
return <div>获取到的值是:{value}</div>;
}}
</Consumer>
</div>
);
}
}
export default Cmp1;
方式2:通过绑定静成属性来消费
import React, { Component } from "react";
import context from "../Context ";
class Cmp2 extends Component {
static contextType = context;
render() {
return <div>{this.context}</div>;
}
}
export default Cmp2;
十一、网络请求
11.1、axios
react中通过npm来安装axios扩展
npm i -S axios
11.2、发起请求
以请求接口地址https://api.i-lynn.cn/ip为例,请求完毕后将当前我们自己的IP地址显示在视图中。
import React, { Component } from "react";
// 引入axios
import axios from "axios";
class App extends Component {
// 初始化状态
state = {
ipInfo: {},
};
render() {
// 从数据中获取ip、country、area
let { ip, country, area } = this.state.ipInfo;
return (
<div>
当前的IP地址是:{ip} - {country} / {area}
</div>
);
}
// 类似于vue中的mounted
async componentDidMount() {
let ret = await axios.get("https://api.i-lynn.cn/ip");
// console.log(ret.data);
// 修改状态
this.setState(() => {
return {
ipInfo: ret.data,
};
});
}
}
export default App;
列表展示
11.3、react的反向代理
在配置在src/setupProxy.js文件,并通过npm安装http-proxy-middleware,代理中间件模块 npm i -S http-proxy-middleware
配置反向代理
const {createProxyMiddleware: proxy} = require('http-proxy-middleware')
module.exports = app => {
app.use('/api', proxy({
target: 'http://localhost',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}))
}
十二、高阶组件(HOC)
高阶函数:
https://blog.csdn.net/weixin_46797477/article/details/108810821
❀ 把函数当作参数传递给另一个函数
❀ 把函数作为另一个函数的返回结果
高阶组件(Higher-Order Components)就是一个函数,传给它一个组件,它返回一个新的组件。
高阶组件:就相当于手机壳,通过包装组件,增强组件功能。
实现步骤:
✿ 首先创建一个函数
✿ 指定函数参数,参数应该以大写字母开头
✿ 在函数内部创建一个类组件或函数组件,提供复用的状态逻辑代码,并返回
✿ 在该组件中,渲染参数组件,同时将状态通过prop传递给参数组件(可选,如有)
✿ 调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面
作用:
✿ 进行权限控制
✿ 路由限制
✿ 访问统计
✿ 统一布局
12.1、传统函数方式调用
# 定义高阶组件
// 定义一个函数,在函数内部创建一个相应类组件
function HocComp(Cmp) {
// 该组件提供复用状态逻辑
class Hoc extends React.Component {
state = {
a: 0,
b: 0
}
render() {
// 把当前组件的状态通过属性传给参数组件
return <Cmp {...this.state} />
}
}
return Hoc
}
# 使用高阶组件
function Cmp(props) {
return (
<p>
a的值:{props.a}
<hr/>
b的值:{props.b}
</p>
)
}
// 组件来进行包装
let Mycmp = HocComp(Cmp)
class App extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
高阶组件
<Mycmp></Mycmp>
</div>
)
}
}
定义和使用高阶组件
对于数据的处理
12.2、使用装饰器调用
装饰器是一种函数,写成 @函数名
。它可以放在类和类方法的定义前面。react脚手架创建的项目默认是不支持装饰器,需要手动安装相关模块和添加配置文件。
✍ 安装相关模块
npm i -D customize-cra react-app-rewired
✍ 修改package.json文件中scripts命令
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-scripts test",
"eject": "react-scripts eject"
}
✍ 在项目根目录中添加config-overrides.js配置文件
此文件可以理解为就是webpack.config.js的扩展文件
const path = require('path')
const {addDecoratorsLegacy, override} = require('customize-cra')
const customize = () => (config) => {
config.resolve.alias['@'] = path.join(__dirname, 'src')
return config
}
module.exports = override(
addDecoratorsLegacy(),
customize()
)
12.3、memoization(计算属性-记忆组件)
连续两次相同传参,第二次会直接返回上次的结果,每次传参不一样,就直接调用函数返回新的结果,会丢失之前的记录,并不是完全记忆,可以在它的参数中传入state数据从而实现了类似Vue中的计算属性功能
# 安装
npm i -S memoize-one
# 引入
import memoizeOne from 'memoize-one'
# 使用
getValue = memoizeOne((x,y) => x+y)
# 调用
render(){
let total = this.getValue(this.state.x, this.state.y)
return <div>{total}</div>
}
十三、css-in-js
13.1、介绍
CSS-in-JS是一种技术,而不是一个具体的库实现。简单来说CSS-in-JS就是将应用的CSS样式写在JavaScript文件里面,而不是独立为一些css,scss或less之类的文件,这样你就可以在CSS中使用一些属于JS的诸如模块声明,变量定义,函数调用和条件判断等语言特性来提供灵活的可扩展的样式定义。CSS-in-JS在React社区的热度是最高的,这是因为React本身不会管用户怎么去为组件定义样式的问题,而Vue有属于框架自己的一套定义样式的方案。
styled-components
应该是CSS-in-JS最热门的一个库,通过styled-components,你可以使用ES6的标签模板字符串语法,为需要styled的Component定义一系列CSS属性,当该组件的JS代码被解析执行的时候,styled-components会动态生成一个CSS选择器,并把对应的CSS样式通过style标签的形式插入到head标签里面。动态生成的CSS选择器会有一小段哈希值来保证全局唯一性来避免样式发生冲突。
13.2、安装
npm i -S styled-components
13.3、定义样式
# 样式js文件
import styled from 'styled-components'
const Title = styled.div`
color:red;
font-size:16px;
h3{
color:blue;
font-size:20px;
}
`
export {
Title
}
# 显示
// 就像使用常规 React 组件一样使用 Title
import React, { Component } from 'react'
import { Title } from './Styles'
class App extends Component {
render() {
return (
<div>
<Title>
我只是一个标题
<h3>你好世界</h3>
</Title>
</div >
);
}
}
export default App
13.4、样式继承
# 样式
import styled from 'styled-components'
const Button = styled.button`
font-size: 20px;
border: 1px solid red;
border-radius: 3px;
`;
// 一个继承 Button 的新组件, 重载了一部分样式
const Button2 = styled(Button)`
color: blue;
border-color: yellow;
`;
export {
Button,
Button2
}
# 显示
import React, { Component } from 'react'
import {
Button,
Button2
} from './Styles'
class App extends Component {
render() {
return (
<div>
<Button>我是一个按钮1</Button>
<Button2>我是一个按钮2</Button2>
</div >
);
}
}
export default App
13.5、属性传递
# 样式
import styled from 'styled-components'
const Input = styled.input`
color: ${props => props.inputColor || "blue"};
border-radius: 3px;
`;
export {
Input
}
# 显示
import React, { Component } from 'react'
import {
Input
} from './Styles'
class App extends Component {
render() {
return (
<div>
<Input defaultValue="你好" inputColor="red"></Input>
</div >
);
}
}
export default App
十四、状态管理(redux)
14.1、简介
https://www.redux.org.cn/
2013年 Facebook 提出了 Flux 架构的思想,引发了很多的实现。2015年,Redux 出现,将 Flux 与函数式编程结合一起,很短时间内就成为了最热门的前端架构。
简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。如果你的项目组件的数量和层级也变得越来越多,越来越深,此时组件间的数据通信就变得异常的复杂和低效,为了解决这个问题,引入了状态管理(redux)从而很好的解决多组件之间的通信问题。
14.2、安装Redux
redux不是内嵌在react框架中,使用时需要手动去安装
npm i -S redux
14.3、三大原则
☃ 单一数据源
整个应用的 state 被储存在一棵对象结构中,并且这个对象结构只存在于唯一一个 store 中
☃ State 是只读的
redux中的state只读的不可以直接修改
☃ 使用纯函数(reducer)来执行修改state
为了修改了state数据,redux定义了一个reducer函数来完成state数据的修改,reducer会接收先前的 state 和 action,并返回 新的 state
操作原理图:
①、store通过reducer创建了初始状态
②、component通过store.getState()获取到了store中保存的state挂载在了自己的状态上
③、用户产生了操作,调用了actions 的方法 ④、actions的方法被调用,创建了带有标示性信息的action(描述对象)
⑤、actions将action通过调用store.dispatch方法发送到了reducer中
⑥、reducer接收到action并根据标识信息判断之后返回了新的state
⑦、store的state被reducer更改为新state的时候,store.subscribe方法里的回调函数会执行,此时就可以通知component去重新获取state
14.4、使用redux
计数器案例:
♞ 创建统一的数据源
在src目录下新建store/index.js作为数据源声明文件
// 常规导入
import { createStore } from "redux"
// 创建默认的数据源(state)
const defaultState = {
// 初始数据
count: 0
}
// 负责处理数据
function reducers(state = defaultState, action) {
// 返回新的state数据
return state
}
// 创建仓库
const store = createStore(reducers)
// 导出
export default store
♞ 组件中获取/设置数据
import React, { Component } from "react"
// 导入仓库
import store from './store'
class App extends Component {
constructor(props){
super(props)
// 获取初始数据
this.state = store.getState()
// 订阅数据(获取更新)
store.subscribe(() => {
this.setState(state => store.getState())
});
}
render() {
return (
<div>
{ /* 渲染的内容 */ }
</div>
);
}
incr() {
// 任务清单
const actionCreator = {
type: 'incr',
payload: 1
}
// 派发任务清单
store.dispatch(actionCreator);
}
}
export default App;
♞ 安装redux-devtools
为了方便调试redux(可选安装),建议去谷歌商店安装redux-devtools
在使用的时候需要参考其说明页面,参考其使用说明给代码做对应的调整。
// 创建仓库
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
随后打开浏览器调试工具就可以看到类似如下界面:
14.5、代码模块化
现在我们已经能够很好的进行redux的数据管理,但是有一个缺点就是所有的代码都写在一个文件中,需要按照模块化开发的规则进行对代码拆分。
store
|- reducer目录
|- methods目录
|- actions目录
|- index.js入口文件
十五、react-redux
网址:https://react-redux.js.org/
React-Redux是Redux的官方针对React开发的扩展库,默认没有在React项目中安装,需要手动来安装。react-redux是依赖于redux,所以你必须安装redux
你可以理解为react-redux就是redux给我们提供一些高阶组件,能解决的问题是:使用它以后我们不需要在每个组件中再去手动订阅数据的更新了。
npm i -S react-redux redux
✪ 定义Provider
在程序主文件index.js文件中,定义Provider
此处类似于之前跨组件通信处的Provider一样,旨在让全局的组件共享store中的数据
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import store from './store'
import App from './App'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
✪ 在子组件中使用react-redux
import { connect } from 'react-redux'
class Cmp extends Component {}
const mapStateToProps = state => ({
state
});
const mapDispatchToProps = dispatch => ({})
export default connect(
mapStateToProps,
mapDispatchToProps
)(Cmp)
在组件中去使用react-redux的方案
十六、redux中间件
16.1、介绍
redux默认支持同步操作,异步操作怎么办?Action发出以后,Reducer立即算出 State,这叫做同步;Action 发出以后,过一段时间再执行 Reducer,这就是异步。怎么才能Reducer 在异步操作结束后自动执行呢?这就要用到新的工具:中间件。中间件有很多,这里使用一个 Redux 官方出品的 中间件库:redux-thunk
16.2、使用redux-thunk
它支持函数返回,本质还是在在内部调用dispatch返回一个固定值对象
npm i -S redux-thunk
在createStore实例store中使用
import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
const store = createStore(
reducer,
applyMiddleware(thunk)
)
export default store
//注:需要注意,加上了以上代码后,redux的调试工具就会失效并且会报错,如果还想使用redux的调试工具,则需要调整代码为
const composeEnhancers = (typeof window !== "undefined" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose
const store = createStore(
reducer,
composeEnhancers(applyMiddleware(thunk))
);
# action
export const incrCount = () => dispatch => {
setTimeout(() => {
dispatch({
type: 'add',
payload: 1
})
}, 1000)
}
十七、Redux模块化
Redux 提供了一个combineReducers方法,用于 Reducer 的拆分
。你只要定义各个子 Reducer 函数,然后用这个方法,将它们合成一个大的 Reducer。
import { combineReducers } from 'redux'
import admin from './reducer/admin'
import web from './reducer/web'
const reducer = combineReducers({
admin,
web
})
在redux入口文件中引入combineReducers方法,进行对于多个reducer合并操作
注:reducer中的action.type名称不要有重复,如要有重复它会执行N次
十八、路由
18.1、介绍
现代的前端应用大多数是SPA(单页应用程序),也就是只有一个HTML页面的应用程序。因为它的用户体验更好、对服务器压力更小,所以更受欢迎。为了有效的使用单个页面来管理多页面的功能,前端路由应运而生。
☄ 前端路由功能:让用户从一个视图(组件)导航到另一个视图(组件)
☄ 前端路由是一套映射规则,在React中,是URL路径与组件的对应关系
☄ 使用React路由简单来说,就是配置路径和组件
18.2、路由使用
https://reactrouter.com/
18.2.1、安装路由模块
路由模块不是react自带模块,需要安装第3方模块
npm i -S react-router-dom
18.2.2、相关组件
☦ Router组件:包裹整个应用,一个React应用只需要使用一次
Router: HashRouter和BrowserRouter
HashRouter: 使用URL的哈希值实现 (localhost:3000/#/first)
BrowserRouter:使用H5的history API实现(localhost3000/first)
☦ Link/NavLink组件:用于指定导航链接(a标签)
最终Link会编译成a标签,而to属性会被编译成 a标签的href属性
☦ Route组件:指定路由展示组件相关信息(组件渲染)
path属性:路由规则,这里需要跟Link组件里面to属性的值一致
component属性:展示的组件
各组件关系示意图
定义项目使用路由,在入口文件/src/index.js文件中定义路由模式
定义路由规则和匹配成功的渲染组件
在浏览器中输入后尝试匹配
18.3、声明式导航
使用Link或NavLink组件完成声明式导航的定义
Link/NavLink区别
❧ Link组件不会根据路由的变化而添加或修改编译后html标签中的属性
❧ NavLink会根据路由的变化而自动修改编译后html标签中的属性
如果当前的路由规则和Navlink中的To所写的规则一致则添加class样式,
默认名称为 active,可以通过activeClassName来修改匹配成功后样式名称。
import React, { Component } from 'react'
import { HashRouter as Router, Route, Link } from 'react-router-dom'
import Home from './pages/Home'
import News from './pages/News'
class App extends Component {
render() {
return (
<Router>
<h3>导航区域</h3>
<hr />
<ul>
<li>
<Link to="/home">首页</Link>
</li>
<li>
<NavLink to="/news">新闻</NavLink>
</li>
</ul>
<hr />
<Route path="/home" component={Home} />
<Route path="/news" component={News} />
</Router>
);
}
}
export default App
18.4、编程式导航
react-router-dom中通过history对象中的push/replace/go等方法实现编程式导航功能。
this.props.history.push({
pathname: "/home",
search: "from=404", // 表示传递查询字符串
state: { // 隐式传参,地址栏不体现
username: "admin",
},
});
注:在react路由中this.props要想得到路由中的对象,则默认必须要通过路由规则匹配渲染的组件才能有此对象 - 必须是直接渲染的组件
18.5、路由参数
路由参数:在Route定义渲染组件时给定动态绑定的参数。
React路由传参方式有三种:
❦ 动态路由参数(param)
以“/detail/:id”形式传递的数据
在落地组件中通过this.props.match.params得到
❦ 查询字符串(query)
通过地址栏中的 ?key=value&key=value传递
在落地组件中通过this.props.location.search得到
❦ 隐式传参(state),通过地址栏是观察不到的
通过路由对象中的state属性进行数据传递
在落地组件中通过this.props.location.state得到
404页面定义
18.6、嵌套路由
在有一些功能中,往往请求地址的前缀是相同的,不同的只是后面一部份,此时就可以使用多级路由(路由嵌套)来实现此路由的定义实现。
例如,路由规则如下
admin/index
admin/user
它们路由前缀的admin是相同的,不同的只是后面一部份。
实现方式:
✌ 先需要定义个组件,用于负责匹配同一前缀的路由,将匹配到的路由指向到具体的模块
<Route path="/admin" component={Admin}></Route>
✌ 创建模块路由组件负责指定各个路由的去向
render() {
// 获取前缀,供后续地址做路由拼接
let prefix = this.props.match.path;
return (
<div>
<h1>欢迎使用后台管理程序</h1>
<Route path={`${prefix}/user`} component={User}></Route>
<Route path={`${prefix}/goods`} component={Goods}></Route>
</div>
);
}
18.7、三种路由渲染方式
18.7.1、component (组件对象或函数)
<Route path="/home" component={Home} />
// 或
<Route path="/home" component={(router)=><Home {…router} />} />
18.7.2、render (函数)
<Route path="/home" render={router=><Home {…router} />} />
18.7.3、children (函数或组件)
// 全匹配
<Route path="/about" children={router =>{
return <div>children渲染</div>
}} />
或
// 精确匹配
<Route path="/about" children={<About />} />
18.8、withRouter高阶组件
作用:把不是通过路由直接渲染出来的组件,将react-router 的 history、location、match 三个对象传入props对象上
默认情况下必须是经过路由匹配渲染的组件才存在this.props,才拥有路由参数,才能使用编程式导航的写法,执行this.props.history.push(’/uri’)跳转到对应路由的页面,然而不是所有组件都直接与路由相连的,当这些组件需要路由参数时,使用withRouter就可以给此组件传入路由参数,此时就可以使用this.props
// 引入withRouter
import { withRouter} from 'react-router-dom'
// 执行一下withRouter
export default withRouter(Cmp)
18.9、自定义导航组件
为何需要自定义导航?
因为在项目中往往不是所有的声明式导航都是需要a标签完成,有时候可能需要别的标签,此时如果在需要的地方去写编程式导航就会有代码重复可能性,就在对于公共代码进行提取。
思路:
☊ 定义一个普通组件可以是类组件也可以是函数式组件
☊ 父组件能向子组件传值 props
☊ 此高阶组件不管路由规则是否匹配都要有渲染 children
十九、过渡动画组件
https://reactcommunity.org/react-transition-group/css-transition
19.1、基础使用
在项目中可能会有一些动画效果展示或是页面切换效果,css动画的方式,比较局限,涉及到一些js动画的时候没法处理了。react-transition-group是react的第三方模块,借住这个模块可以更方便的实现更加复杂的动画效果
۞ 安装
npm i -S react-transition-group
۞ 使用
state = {
show: true
}
//执行动画
handleToggle = () => {
this.setState({
show: !this.state.show
})
}
render(){
return (
<CSSTransition
in={this.state.show} //控制动画是否入场,为true时,动画进场,为false时动画出场
timeout={1000} //动画执行时间
classNames: '样式前缀名称'
unmountOnExit //元素退场时,自动把DOM也删除
>
<div>玩转React Transition</div>
</CSSTransition>
<button onClick={this.handleToggle}>Animation</button>
)
}
// 自定义样式
.wuchen-enter {
opacity: 0;
}
.wuchen-enter-active {
opacity: 1;
transition: opacity 200ms;
}
.wuchen-exit {
opacity: 1;
}
.wuchen-exit-active {
opacity: 0;
transition: opacity 200ms;
}
19.2、结合animate.css
animate.css动画库集成到react-transation-group动画模块中
网址:https://animate.style/
通过cdn地址下载动画库
放到项目中的src/assets目录中
集成到CssTransation组件中
<CSSTransition
in={this.state.show} //控制动画是否入场,为true时,动画进场,为false时动画出场
timeout={1000} //动画执行时间
//自定义的类名(定义动画效果,进场前,进场后直到结束,结束前,结束后)
classNames={{
enter: 'animate__animated',
enterActive: 'animate__fadeIn',
exit: 'animate__animated',
exitActive: 'animate__fadeOut'
}}
unmountOnExit //元素退场时,自动把DOM也删除
>
<div>玩转React Transition</div>
</CSSTransition>
19.3、列表过渡
<TransitionGroup>
<CSSTransition
key={变量} // 通过此key值来判断列表中的子节点需要被插入还是移除,然后触发动画
timeout={1000} //动画执行时间
//自定义的类名(定义动画效果,进场前,进场后直到结束,结束前,结束后)
classNames={{
enter: 'animate__animated',
enterActive: 'animate__fadeIn',
exit: 'animate__animated',
exitActive: 'animate__fadeOut'
}}
unmountOnExit //元素退场时,自动把DOM也删除
>
<div>玩转React Transition</div>
</CSSTransition>
<CSSTransition
key={变量} // 通过此key值来判断列表中的子节点需要被插入还是移除,然后触发动画
timeout={1000} //动画执行时间
//自定义的类名(定义动画效果,进场前,进场后直到结束,结束前,结束后)
classNames={{
enter: 'animate__animated',
enterActive: 'animate__fadeIn',
exit: 'animate__animated',
exitActive: 'animate__fadeOut'
}}
unmountOnExit //元素退场时,自动把DOM也删除
>
<div>玩转React Transition</div>
</CSSTransition>
</TransitionGroup>
19.4、路由过渡
路由切换时添加动画效果
render() {
return (
<div>
<li><Link to="/home">Home</Link></li>
<li><Link to="/child">Child</Link></li>
<TransitionGroup>
<CSSTransition
timeout={1000}
classNames='fade'
unmountOnExit
// 通过此key值来判断列表中的子节点需要被插入还是移除,然后触发动画
// location.key属性不是一定有,只有路由模式为history时才有
// hash可以用 uri
key={this.props.location.key}
>
<Switch>
<Route path="/home" component={Home} />
<Route path="/child" component={Child} />
</Switch>
</CSSTransition>
</TransitionGroup>
</div>
)
}
19.5、利用高阶组件给组件添加动画
并不想让所有的路由都有动画效果,只是想对指定的页面有路由切换效果,可以利用高阶组件来完成。让组件有动画
# 定义高阶组件
import React, { Component } from 'react'
import { CSSTransition } from 'react-transition-group'
//import '../assets/animate.css'
const withAnimation = Cmp => {
return class extends Component {
render() {
return (
<CSSTransition
in={this.props.match !== null}
timeout={600}
classNames={{
enter: 'animate__animated',
enterActive: 'animate__fadeIn',
exit: 'animate__animated',
exitActive: 'animate__fadeOut'
}}
unmountOnExit
>
<Cmp {...this.props} />
</CSSTransition>
)
}
}
}
export default withAnimation
# 使用
@withAnimation
class Page extends Component {
render() {
return <div>高阶组件完成路由切换动画效果</div>
}
}
// 使用高阶组件定义路由动画组件是一定要用它的Route children来渲染组件
<Route path="/home" children={props => <Page {...props} />} />
二十、immutable.js
20.1、概述
官网:https://immutable-js.github.io/immutable-js/
https://cloud.tencent.com/developer/section/1489374
Immutable.js出自Facebook,是最流行的不可变数据结构的实现之一。它实现了完全的持久化数据结构,使用结构共享。所有的更新操作都会返回新的值,但是在内部结构是共享的,来减少内存占用(和垃圾回收的失效)。
持久化数据结构:这里说的持久化是用来描述一种数据结构,指一个数据,在被修改时,仍然能够保持修改前的状态,即不可变类型。
结构共享:Immutable使用先进的tries(字典树)技术实现结构共享来解决性能问题,当我们对一个Immutable对象进行操作的时候,ImmutableJS会只clone该节点以及它的祖先节点,其他保持不变,这样可以共享相同的部分,大大提高性能。
惰性操作:创建的对象时其实代码块没有被执行,只是被声明了,代码在获取或修改的时候才会实际被执行
20.2、使用immutable优缺点
¶ 优点
降低mutable带来的复杂度
节省内存
历史追溯性
拥抱函数式编程
¶ 缺点
需要重新学习api
资源包大小增加(源码5000行左右)
容易与原生对象混淆:由于api与原生不同,混用的话容易出错
第3方组件库不支持,使用时,还需要转换
20.3、安装
npm i -S immutable
20.4、常用Api
Map(): 原生object转Map对象
const map1 = Map({ a: 1, b: 2, c: 3})
const map2 = Map({ a: 1, b: 2, c: 3})
console.log(map1 === map2) // false
console.log(map1.equals(map2)) // true
map.toJS() // 把map转为js对象
--------------------------------------------------------
List(): 原生array转List对象
const list1 = List([1, 2]);
const list2 = list1.push(3, 4, 5);
// 获取值
console.log(list2.get(0));
const list1 = List([ 1, 2, 3 ]);
const list2 = List([ 4, 5, 6 ]);
const list3 = list2.concat(list1);
console.log(list3.toArray())
--------------------------------------------------------
fromJS(): 原生js转immutable对象
const imState = fromJS({
name: 'lisi',
users: ['aa', 'bb']
})
# 获取数据
imState.get('name')
console.log(imState.get('users').get(1))
console.log(imState.getIn(['users', 0]))
--------------------------------------------------------
toJS(): immutable对象转原生js 不推荐使用
const state = imState.toJS()
console.log(state);
--------------------------------------------------------
set/setIn/update/updateIn 修改数据
const newState = imState.set('name', 'zhangsan')
const newState = imState.setIn(['name'], 'zhangsan')
const newState = imState.update('count', value => value + 1)
const newState = imState.updateIn(['count'], value => value + 1)
20.5、redux中集成immutable.js
♌ 安装redux-immutable
redux中利用combineReducers来合并reducer并初始化state,redux自带的combineReducers只支持state是原生js形式的,所以需要使用redux-immutable提供的combineReducers来替换原来的方法。
`
npm i -S redux-immutable
♌ 使用
它的合并是支持immutable对象合并
import { combineReducers } from 'redux-immutable'
把所有的reducer数据转换为immutable对象
import {fromJS} from 'immutable'
const defaultState = fromJS({})
二十一、项目
一、 **菜谱大全
1.1、项目背景
当下回家吃饭健康饮食的理念正在兴起。据调查显示,有超过九成的都市白领及年轻人其实都倾向于在家里吃饭,尤其是有小孩的家庭意愿会更加强烈, 因为他们普遍都认为在家里吃饭的幸福感会更高; 随着经济的快速发展,人们的生活水平的逐渐提高,对饮食质量要求也越来越高,但都市快节奏的生活让上班族们吃饭的目标性更小,通常只是到了时间随机选 择食物塞饱肚子。该美食网站倡导一种全新的健康的生活方式,用户可以根据网站上提供的食谱了解不同菜系的风格、做法及搭配,除了可以查看各种食谱学习做饭, 还可以在线与其他用户一起交流和分享做菜的心得,通过美食来感受生活之美。
1.2、技术栈
使用react框架来完成本次项目的实现,使用技术有如下一些:
nodejs
react
react-router-dom
redux react-redux redux-thunk immutable redux-immutable
styled-components
antd-mobile
react-transition-group
axios
http-proxy-middleware
配置装饰器(costomize-cra react-app-rewired) 等等
1.3、开发环境
开发环境为:windows
开发工具:vscode/webstorm + jsx插件 + eslint
开发调试工具:chrome浏览器
开发运行环境:node环境
代码管理:git
上线环境:linux + nginx
1.4、项目效果展示
1.5、项目初始化
❶ 在本机磁盘中指定位置创建一下react项目,命令如下
npx create-react-app cookbook
❷ 创建好项目后,进入项目目录先安装常规要使用的三方包,命令如下
npm i -D customize-cra react-app-rewired http-proxy-middleware
npm i -S redux react-redux redux-thunk styled-components immutable redux-immutable react-router-dom react-transition-group axios
❸ 清理创建好的项目中不需要的文件及文件夹
* 删除public目录下的部份内容
* 删除src目录下的部份内容
❹ 修改public/index.html
❺ 在src目录下创建根组件App.jsx与项目入口文件index.js
❻ 配置装饰器支持
// 在当前项目根目录下面创建一个名称为config-overrides.js文件,对webpack进行配置
const {
override,
addDecoratorsLegacy,
addWebpackAlias
} = require("customize-cra");
const path = require("path");
module.exports = override(
addDecoratorsLegacy (),
// 添加webpack别名
addWebpackAlias({
["@"]: path.resolve("./src"),
})
);
❼ 修改package.json中的脚本命令为如下
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
}
❽ 配置反向代理
// 在src目录下创建一个名称为setupProxy.js文件,提前为后续接口设置反向代理
const { createProxyMiddleware: proxy } = require("http-proxy-middleware");
module.exports = app => {
app.use(
"/api",
proxy({
target: "http://localhost:9000",
changeOrigin: true,
pathRewrite:{
"^/api": ""
}
})
)
}
二、首页开发
2.1、Ant Design Mobile组件库
在线文档:https://mobile.ant.design/docs/react/introduce-cn
antd-mobile是Ant Design的移动规范的 React实现,服务于蚂蚁金服及口碑无线业务。它支持多平台,组件丰富、能全面覆盖各类场景,UI 样式高度可配置,拓展性更强,轻松适应各类产品风格。
☀ 在使用前需要先对包进行安装
npm i -S antd-mobile
☀ 按需加载组件代码和样式的 babel 插件
// 按需加载 tree-shaking
npm i -D babel-plugin-import
// config-overrides.js 用于修改默认配置
const { override, fixBabelImports } = require('customize-cra')
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd-mobile',
style: 'css',
})
)
☀ 使用
import React, { Component } from "react";
// 引入`antd-mobile`的按钮组件
import { Button } from "antd-mobile";
class App extends Component {
render() {
return (
<>
<Button type="primary">我是一个常规按钮</Button>
</>
);
}
}
export default App;
2.2、底部导航实现
参考地址:https://mobile.ant.design/components/tab-bar-cn/
底部导航可以使用antd-mobile中的tab-bar组件完成此功能展示。
2.3、顶部导航
height: .4rem;
line-height: .4rem;
background: #FF6C0C;
font-size: .18rem;
text-align: center;
color:#fff;
2.4、轮播显示
参考地址:https://mobile.ant.design/components/carousel-cn
该功能可以使用antd-mobile中的Carousel组件
实现步骤:
在首页组件中引入滑块组件
编写滑块组件实现对应效果
2.5、mock数据
mock数据(faker数据),即使用假数据来模拟后台的数据。
为什么要做假数据?因为后端开发接口并产出接口文档没有哪么快,此时就需要我们自己来模拟请求数据。模拟的数据字段、格式等,需要和后端工程师沟通。这样,我们可以先通过模拟的数据继续完成前端的工作任务,待后端工程师写好数据接口并提供了接口信息后,我们只需要修改请求地址,前后端就实现了无缝衔接。
☞安装json-server帮助我们快速启动一个web服务
npm i -g json-server
// data.json
{
"data": {
"id": 1,
"name": "aaa",
"age": 20
}
}
json-server data.json
☞ 配置mock
☞☞ 创建src/mock/mock.js文件(文件名并非固定)
// commonJs模块化
// json-server配置文件
const swiper = require("./swiper.json");
// 导出
module.exports = () => ({
swiper,
});
☞☞ 创建mock的路由文件src/mock/route.json
{
"/api/swiper": "/swiper"
}
☞ ☞ 在package.json文件中的scripts中配置启动命令
{
scripts:{
"mock": "json-server ./src/mock/mock.js -r ./src/mock/router.json --port=9090"
}
}
☞ 启动web服务
npm run mock
2.6、网络请求封装
封装一下网络请求的方法集合
反向代理配置一下
请求配置的请求方法编写
2.7、搜索组件
export const SearchBox = styled.div`
width: 90vw;
height: .46rem;
display: flex;
border: 1px solid #ff6c0c;
margin: .15rem auto;
border-radius: 5px;
box-shadow: 1px 1px 5px #ccc;
justify-content: center;
align-items: center;
img{
width: .2rem;
height: .2rem;
}
span{
color:#555;
margin-left: .1rem;
}
`
2.8、热门分类
参考地址:https://mobile.ant.design/components/grid-cn/
在antd-mobile组件库中查找到对应的ui组件,使用已存在的组件来简化静态的布局
export const HotCateBox = styled.div`
background: #fff;
.title{
padding: .15rem;
color:#949494;
}
`
<Grid data={hotcate}
columnNum={3}
itemStyle={{ height: '.5rem' }}
onClick={(row, index) => {
console.log(index, this.props.history.push)
}}
renderItem={dataItem => (
<div>{dataItem.title}</div>
)}
/>
2.9、精品好菜
静态布局展示
html
<div>
<h1>精品好菜</h1>
<div>
<dl>
<dt>
<img src="http://www.mobiletrain.org/images/index/new_logo.png" />
</dt>
<dd>
<h3>青椒香干</h3>
<p>1000浏览 2000收藏</p>
</dd>
</dl>
</div>
</div>
css
div{
padding-left: .1rem;
> h1 {
height: .5rem;
line-height: .6rem;
padding-left: .15rem;
color: #666;
padding-left: 0;
}
> div {
display: flex;
flex-wrap: wrap;
> dl {
width: calc(50% - 0.1rem);
background: #fff;
margin-right: .1rem;
margin-bottom: .1rem;
dt {
img {
width: 100%;
}
}
dd {
display: flex;
flex-direction: column;
align-items: center;
padding: .1rem;
h3 {
font-size: .16rem;
}
p {
font-size: .12rem;
color: #666;
}
}
}
}
2.9.1、图片懒加载
npm i -S react-lazyload
三、分类开发
3.1、分类顶部切换
创建需要的组件和样式
html
<ul>
<li class="active">分类</li>
<li>食材</li>
<li class="slider right"></li>
</ul>
css
div{
height:.44rem;
background:#ee742f;
display:flex;
align-items:center;
justify-content:center;
ul{
width:1.4rem;
height:.3rem;
display:flex;
position:relative;
border:1px solid #fff;
z-index:0;
border-radius:.15rem;
li{
flex:1;
line-height:.3rem;
text-align:center;
color:#fff;
&:last-child{
position:absolute;
width:50%;
height:.3rem;
background:#fff;
z-index:-1;
border-radius:.15rem;
transform:translate(0,0);
transition:all 0.4s ease-in;
&.right{
transform:translate(100%,0);
}
}
&.active{
color:#ee742f;
}
}
}
3.2、列表展示
html
<div>
<div>
<ul>
<li class="active"><span>分类</span></li>
</ul>
</div >
<div>
<ul>
<li>内容</li>
</ul>
</div>
</div>
css
.div{
height:100%;
display:flex;
> div:first-child{
width:.9rem;
> ul{
height:100%;
overflow-y:scroll;
li{
height:.5rem;
text-align:center;
line-height:.5rem;
background:#f3f3f3;
&.active{
background:#fff;
span{
display:inline-block;
height:100%;
border-bottom:1px solid #ee742f;
}
}
}
}
}
>div:last-child{
flex:1;
background:#fff;
padding:.2rem .1rem;
>ul{
display:flex;
flex-wrap:wrap;
overflow-y:scroll;
height:100%;
align-content:flex-start;
li{
width:33.3333%;
text-align:center;
height:.5rem;
line-height:.5rem;
color:#666;
}
}
}
3.2、列表展示
html
<div>
<div>
<ul>
<li class="active"><span>分类</span></li>
</ul>
</div >
<div>
<ul>
<li>内容</li>
</ul>
</div>
</div>
css
.div{
height:100%;
display:flex;
> div:first-child{
width:.9rem;
> ul{
height:100%;
overflow-y:scroll;
li{
height:.5rem;
text-align:center;
line-height:.5rem;
background:#f3f3f3;
&.active{
background:#fff;
span{
display:inline-block;
height:100%;
border-bottom:1px solid #ee742f;
}
}
}
}
}
>div:last-child{
flex:1;
background:#fff;
padding:.2rem .1rem;
>ul{
display:flex;
flex-wrap:wrap;
overflow-y:scroll;
height:100%;
align-content:flex-start;
li{
width:33.3333%;
text-align:center;
height:.5rem;
line-height:.5rem;
color:#666;
}
}
}
上线
配置nginx
hosts
四、hook
4.1、简介
在 React 的世界中,有容器组件和 UI 组件之分,在 React Hooks 出现之前,UI 组件我们可以使用函数,无状态组件来展示 UI,而对于容器组件,函数组件就显得无能为力,我们依赖于类组件来获取数据,处理数据,并向下传递参数给 UI 组件进行渲染。React在v16.8 的版本中推出了 React Hooks 新特性,Hook是一套工具函数的集合,它增强了函数组件的功能,hook不等于函数组件,所有的hook函数都是以use开头。
使用 React Hooks 相比于从前的类组件有以下几点好处:
◐ 代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者不利于维护和迭代,通过 React Hooks 可以将功能代码聚合,方便阅读维护
◐ 组件树层级变浅,在原本的代码中,我们经常使用 HOC/render/Props 等方式来复用组件的状态,增强功能等,无疑增加了组件树层数及渲染,而在 React Hooks 中,这些功能都可以通过强大的自定义的 Hooks 来实现
4.2、使用hook限制
◐ hook只能用在函数组件中,class组件不行
◐ 普通函数不能使用hook
◐ 函数组件内部的函数也不行
◐ hook定义时要注意先后顺序
◐ hook函数一定要放在函数组件的第一层,别放在if/for中
4.3、常用hook函数
4.3.1、useState
保存组件状态,功能类似于类组件中的this.state状态管理
import React, { useState } from 'react'
// useState 参数值,为数组1的默认值
// 对于一些有一些需要初始计算的可以使用回调函数的方式来初始值
// let [count, setCount] = useState(() => 0)
// 固定值初始值
let [count, setCount] = useState(0)
// jsx
<button onClick={() => setCount(count + 1)}>+++</button>
4.3.2、useEffect
此hook可以模拟函数组件的生命周期,函数组件对于在一些生命周期中操作还是无能为力,所以 React提供了 useEffect 来帮助开发者处理函数组件,来帮助模拟完成一部份的开发中非常常用的生命周期方法。常被别的称为:副作用处理函数。此函数的操作是异步的。
// useEffect 相当类组件中的3个生命周期 componentDidMount componentDidUpdate componetWillUnMount
import React, { useState, useEffect } from 'react'
const App = () => {
let [count, setCount] = useState(0)
// 参数1:回调函数
// 如果没有第2个参数,表示 componentDidMount componentDidUpdate
// 如果第2个参数为空数组 表示 componentDidMount
// 如果第2个参数不空数组,只关注所写变量事件更新和挂载 componentDidMount componentDidUpdate
useEffect(() => {
if (count > 10) {
document.title = count
}
// 返回回调函数中就是 componetWillUnMount
// 在执行下一个 effect 之前,上一个 effect 就已被清除
return () => {
// 在此处,就可以把setTimout setInterval 清空
}
}, [count]);
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>+++</button>
</div>
);
}
4.3.3、useReducer
useReducer 这个 Hooks 在使用上几乎跟 Redux一模一样,唯一缺少的就是无法使用 redux 提供的中间件。
import React, { useState, useReducer } from 'react'
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
const App = () => {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<div>
<h3>{state.count}</h3>
<button onClick={() => dispatch({ type: 'increment' })}>+++</button>
<button>---</button>
</div>
);
4.3.4、useContext
使用useContext可以方便我们获取Context中的数据源
import React, { useContext } from 'react'
import cxt from '../context/userContext'
const { Provider } = cxt
const App = () => {
return (
<div>
<Provider value='hello'>
<Cmp />
</Provider>
</div>
)
}
function Cmp() {
const context = useContext(cxt)
return (
<h1>
{context}
</h1>
)
}
4.3.5、useMemo
记忆组件,可以理解为计算属性
import React, { useState, useMemo } from 'react';
const Memo = () => {
const [count, setCount] = useState(1)
const [val, setValue] = useState('')
// 返回上一次缓存的值
const sum = useMemo(() => {
return count + 10
}, [count]);
return (
<div>
<h3>{count}-{val}-{sum}</h3>
<div>
<button onClick={() => setCount(count + 1)}>+ count</button>
<input value={val} onChange={event => setValue(event.target.value)} />
</div>
</div>
)
}
4.3.6、useCallback
记忆函数,它计算返回一个缓存函数。
import React, { useState, useCallback, useEffect } from 'react';
const App = () => {
const [count, setCount] = useState(1);
const [val, setVal] = useState('');
const callback = useCallback(() => {
return count
}, [count])
return (
<div>
<h1>父组件:{count}</h1>
<Child callback={callback} />
<div>
<button onClick={() => setCount(count + 1)}>+count</button>
<input value={val} onChange={event => setVal(event.target.value)} />
</div>
</div>
)
}
function Child({ callback }) {
const [count, setCount] = useState(() => callback())
useEffect(() => {
setCount(callback())
}, [callback]);
return (
<div>
<hr />
<div>子组件:{count}</div>
</div>
)
}
4.3.7、useRef
useRef 跟 createRef 类似,都可以用来生成对 DOM 对象的引用
import React, { useRef } from 'react'
const Ref = () => {
const username = useRef()
const login = () => {
console.log(username.current.value);
}
return (
<div>
<div>
<input type="text" ref={username} />
<br />
<br />
<button onClick={login}>添加用户</button>
</div>
</div>
)
}
4.3.8、useImperativeHandle
使用它可以透传 Ref,因为函数组件没有实例,所以在默认自定义函数组件中不能使用ref属性,使用为了解决此问题,react提供了一个hook和一个高阶组件完帮助函数组件能够使用ref属性。
import React, { useRef, useEffect, useImperativeHandle, forwardRef } from "react"
function ChildInputComponent(props, useRef) {
// const inputRef = useRef(null)
//useImperativeHandle(ref, () => inputRef.current)
// 穿透ref
useImperativeHandle(ref, () => ({id:1,name:’aaa’}))
return <input type="text" ref={ useRef } />
}
const ChildInput = forwardRef(ChildInputComponent)
function App() {
const inputRef = useRef(null)
const getValue = () => {
console.log(inputRef.current.value)
}
return (
<div>
<ChildInput ref={inputRef} />
<hr/>
<button onClick={getValue}>获取一下数据</button>
</div>
)
}
4.3.9、useLayoutEffect
大部分情况下,使用 useEffect 就可以帮我们处理组件的副作用,但是如果想要同步调用一些副作用,比如对 DOM 的操作,就需要使用 useLayoutEffect,useLayoutEffect 中的副作用会在 DOM 更新之后同步执行。
import React, { useState, useLayoutEffect, useEffect } from 'react'
const Layout = () => {
const [value, setValue] = useState(0)
useLayoutEffect(() => {
const title = document.querySelector("#title")
console.log("useLayoutEffect");
setValue(title.innerHTML);
})
useEffect(() => {
console.log("useEffect")
})
return (
<div>
<div><h1 id="title">hello</h1><h2>{value}</h2></div>
</div>
)
}
4.3.10、react-redux-hook
react-redux支持了hook的解决方案,提供两个方法,很方便我们在项目去使用
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
const MyRedux = () => {
const { username, password } = useSelector(state => state.userinfo)
const dispatch = useDispatch()
return (
<div>
<h3>{username}</h3>
<hr />
<button onClick={
e => {
dispatch({
type: 'setname',
name: 'aaaaa'
})
}
}>修改一下姓名</button>
</div>
)
}
4.3.11、react-router-dom-hook
react-router-dom也提供了hook的解决方案
import {useHistory,useLocation,useParams} from 'react-router-dom'
4.4、自定义hook
定义自定义hook以use开头 + 函数名称,通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。
import React, {useState, useEffect} from 'react'
// 是否在线
function useOnline() {
const [online, setOnline] = useState(navigator.onLine)
useEffect(() => {
const handlerOnline = () => setOnline(true)
const handlerOffline = () => setOnline(false)
window.addEventListener('online', handlerOnline, false)
window.addEventListener('offline', handlerOffline, false)
return () => {
window.removeEventListener('online', handlerOnline, false)
window.removeEventListener('offline', handlerOffline, false)
}
})
return online
}
function App() {
const online = useOnline()
return (
<div>
{
online ?
<h3 style={{color: 'green'}}>在线</h3>
:
<h3>离线了 </h3>
}
</div>
)
}