网络请求-高阶组件-组件样式定义
一、网络请求
- 发起请求
假设我们向这个网址发起数据请求:https://api.iynn.cn/film/api/v1/getNowPlayingFilmList?cityId=110100&pageNum=1&pageSize=10
在src文件中会创建一个utils文件夹,然后在文件中创建一个http.js
import axios from 'axios'
// 多实例创建axios请求
const ins = axios.create()
// data简化处理
ins.interceptors.response.use(
res => res.data,
err => {
// 请求错误提示
return Promise.reject(err)
}
)
// 请求拦截器
ins.interceptors.request.use(config => {
// 请求的超时时长
config.timeout = 10000
// 要想让它走代理,则一定开发时不要配置
// config.baseURL = 'http://127.0.0.1:9000'
return config
})
/**
* get请求
* @param {string} url 请求的url地址
* @param {object} config 请求的额外头信息
* @returns Promise<any>
*/
export const get = (url, config = {
}) => ins.get(url, config)
/**
* post请求
* @param {string} url 请求的url地址
* @param {json|urlEncoded|formData} data 请求体数据 formData[Content-Type=multipart/form-data]
* @param {object} config 请求的额外头信息
* @returns Promise<any>
*/
export const post = (url, data = {
}, config = {
}) => ins.post(url, data, config)
/**
* delete请求
* @param {string} url 请求的url地址
* @param {object} config 请求的额外头信息
* @returns Promise<any>
*/
export const del = (url, config = {
}) => ins.delete(url, config)
/**
* put请求
* @param {string} url 请求的url地址
* @param {json|urlEncoded|formData} data 请求体数据 formData[Content-Type=multipart/form-data]
* @param {object} config 请求的额外头信息
* @returns Promise<any>
*/
export const put = (url, data = {
}, config = {
}) => ins.put(url, data, config)
在src文件中会创建一个service文件夹,然后在文件中创建一个filmApi.js
import {
get } from '@/utils/http'
// 获取正在热映的电影
export const getNowPlayingApi = async () => {
let ret = await get('/api/v1/getNowPlayingFilmList?cityId=110100&pageNum=1&pageSize=10')
return ret
}
在组件中
import React, { Component } from 'react'
import { getNowPlayingApi } from './service/filmApi'
class App extends Component {
state = {
// 如果你初始值为null,则在下面一定要进行条件渲染
films: []
}
async componentDidMount() {
let ret = await getNowPlayingApi()
// 更新state数据
this.setState({
films: ret.data.films
})
}
render() {
return (
<div>
<h3>电影列表</h3>
<ul>
{this.state.films.map(item => (
<li key={item.filmId}>{item.name}</li>
))}
</ul>
</div>
)
}
}
export default App
- react代理实现跨域
有两种方式,第一种实在src文件创建setupProxy.js
此文件它是由当前的create-react-app脚手架提供的一个用于设置本项目开发时的网络请求代理的配置文件
它是运行的nodejs环境下的一个配置文件,它支持的语法是CommonJS语法
要想让他实现开发代理,还需要手动安装一下代理库 http-proxy-middleware
安装: yarn add -D http-proxy-middleware
修改此配置文件后,一定要重启本项目,才能生效
const {
createProxyMiddleware: proxy } = require('http-proxy-middleware')
// app对象它就是express对象
// 利用app对象用来它设置网络请求代理,或mock请求数据
module.exports = app => {
// mock示例
app.get('/api/users', (req, res) => {
res.send({
code: 0,
msg: 'ok',
data: 1
})
})
// 网络请求代理
app.use(
'/api',
proxy({
target: 'https://api.iynn.cn/film',
changeOrigin: true
// pathRewrite:{
// '^/api': ''
// }
})
)
}
第二种
在react中,在项目文件下创建craco.config.js,需要下载
此文件运行在nodejs环境中,使用commonJS语法
此文件就是对当前项目的webpack进行增量配置
修改此文件一定要重启项目
https://4x.ant.design/docs/react/use-with-create-react-app-cn#eject
官网:https://craco.js.org
https://www.jianshu.com/p/5fd396e6e448
npm add -D @craco/craco
const path = require('path')
module.exports = {
webpack: {
alias: {
// 将 @ 别名指向你的源代码目录
'@': path.resolve('src')
}
},
devServer: {
setupMiddlewares: (middlewares, {
app }) => {
app.get('/api/users', (req, res) => {
res.send({
code: 0,
msg: 'ok',
data: 1000
})
})
return middlewares
},
proxy: {
'/api': {
target: 'https://api.iynn.cn/film',
changeOrigin: true
// pathRewrite: { '^/api': '' }
}
}
}
}
二、组件传值
props父子间传值
父组件将自己的状态和修改的方法传递给子组件,子组件当做属性来接收,当父组件更改自己状态的时候,子组件接收到的属性就会发生改变,子组件也可以调用父组件通过属性传过来的方法,完成对于数据的修改
前面的笔记中对于props已经很详细了,下面主要介绍ref的组件传值
ref实现父子间传值
父组件利用ref对子组件做标记,通过调用子组件的方法以更改子组件的状态,也可以调用子组件的方法(说明:ref获取当前组件对象,只针对的是使用类创建的组件才可以用此方案,类有实例,而函数没有实例,默认只针对于类组件)
import React, { Component, createRef } from 'react'
// ref如果它绑定在自定义类组件中,得到当前绑定的类组件的实例,通过实例获取它里面的方法或属性从而实现父子间的传值
// 注: ref默认绑定在函数组件上会报错,不允许直接去绑定
class Child extends Component {
state = {
count: 100
}
setCount = n => {
this.setState({ count: n })
return '给你'
}
render() {
return (
<div>
<div>child组件 -- {this.state.count}</div>
</div>
)
}
}
class App extends Component {
childRef = createRef()
addCount = () => {
console.log(this.childRef.current.state.count)
const msg = this.childRef.current.setCount(Date.now())
console.log(msg)
}
render() {
return (
<div>
<h3>App组件</h3>
<button onClick={this.addCount}>++count++</button>
<hr />
{/* 把ref对象绑定在自定义类组件上,它就可以获取它的实例 */}
<Child ref={this.childRef} />
</div>
)
}
}
export default App
兄弟组件[状态提升]
此方案用来解决兄弟组件间的传值问题,就是把共用的状态信息提升到父组件状态中。
import React, { Component } from 'react'
class Child1 extends Component {
render() {
return (
<div>
<div>Child1组件 -- {this.props.count}</div>
</div>
)
}
}
class Child2 extends Component {
render() {
return (
<div>
<div>Child2组件</div>
<button
onClick={() => {
this.props.setCount(Date.now())
}}
>
++++++++
</button>
</div>
)
}
}
class App extends Component {
// 通过状态提升,实现兄弟组件间的通信
// 把兄弟组件他们要操作的数据提升到他们的父组件上
state = { count: 100 }
setCount = count => {
this.setState({ count })
}
render() {
return (
<div>
<h3>App组件</h3>
<hr />
<Child1 count={this.state.count} />
<Child2 setCount={this.setCount} />
</div>
)
}
}
export default App
跨组件通信
在react没有类似vue中的事件总线来解决这个问题。在实际的项目中,当需要组件间跨级访问信息时,如果还使用组件层层传递props,此时代码显得不那么优雅,甚至有些冗余。在react中,我们还可以使用context来实现跨级父子组件间的通信。
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据。
在React的Context中,数据我们可当成商品,发布数据的组件会用provider身份(卖方),接收数据的组件使用consumer身份(卖方)
import React, { Component } from 'react'
import context, { Provider, Consumer } from './context/appContext'
class Child1 extends Component {
render() {
return (
<div>
<div>Child1组件</div>
<Consumer>
{state => {
return state.count
}}
</Consumer>
</div>
)
}
}
class Child2 extends Component {
// context在类组件中还可以简化调用
// 如下赋值后,可以他们给类的成员属性 this.context赋值
static contextType = context
render() {
return (
<div>
<div>Child2组件</div>
{/* <Consumer>
{state => {
return (
<button
onClick={() => {
state.setCount(Date.now())
}}
>
++++++++
</button>
)
}}
</Consumer> */}
<button
onClick={() => {
this.context.setCount(Date.now())
}}
>
++++++++
</button>
</div>
)
}
}
class App extends Component {
// 通过状态提升,实现兄弟组件间的通信
// 把兄弟组件他们要操作的数据提升到他们的父组件上
state = { count: 100 }
setCount = count => {
this.setState({ count })
}
render() {
return (
<Provider
value={
{
count: this.state.count,
setCount: this.setCount
}}
>
<h3>App组件</h3>
<hr />
<Child1 />
<br />
<Child2 />
</Provider>
)
}
}
export default App
在src文件夹下创建context文件夹,在文件夹中创建appContext.js
// 创建一个context对象,用来进行跨层级的数据传递
import {
createContext } from 'react'
const context = createContext()
// Provider 组件,用来把给定数据向它包裹的子孙组件传递下去
// Consumer 组件,用来接收上层组件传递下来的数据[Provider传下来的数据]
export const {
Provider, Consumer } = context
export default context
三、高阶组件
高阶组件(Higher-Order Components)就是一个函数,传给它一个组件,它返回一个新的组件。
高阶组件:就相当于手机壳,通过包装组件,增强组件功能。
js高阶函数:①、把函数当作形参;②、返回一个函数
实现步骤:
- 创建一个函数
- 指定函数参数,参数应该以大写字母开头,参数中要传入一个组件
- 在此函数内部创建一个类组件或函数组件
高阶组件常见作用:
- 进行权限控制
- 访问统计
- 统一布局
缺点:增加了组件层级,影响性能。
定义高阶组件(HOC)
在src文件中创建一个context文件夹,创建appCountext.js文件
// 创建一个context对象,用来进行跨层级的数据传递
import {
createContext } from 'react'
const context = createContext()
// Provider 组件,用来把给定数据向它包裹的子孙组件传递下去
// Consumer 组件,用来接收上层组件传递下来的数据[Provider传下来的数据]
export const {
Provider, Consumer } = context
export default context
在src目录下创建一个hoc文件夹,创建withLayout.js文件
// 定义高阶组件,来统一布局
// 1. 定义一个函数,此函数接收一个组件[类组件也可以是函数组件]作为参数,参数首字母大写
// 2. 返回一个增强后的组件
// 高阶组件缺点:增加原来已有组件的层级,从而影响性能
// 优点:把业务进行提取,统一处理
import React, {
Component } from 'react'
// 传入过来的组件
// const withLayout = Cmp => {
// // return class Layout extends Component {
// // 匿名类
// return class extends Component {
// render() {
// return (
// <>
// <div style={
{ color: 'red' }}>我是顶部导航</div>
// {/*
// 要把原来父组件传过来的props数据向下传递给子组件
// this.props
// */}
// {/* 手动一个个传入给子 */}
// {/* <Cmp a={this.props.a} b={this.props.b} /> */}
// {/* 把所有的props对象中的数据向下传递 */}
// <Cmp {...this.props} />
// <footer>底部声明</footer>
// </>
// )
// }
// }
// }
// ------------ 函数组件
// const withLayout = Cmp => {
// return props => {
// return (
// <>
// <div style={
{ color: 'blue' }}>我是顶部导航</div>
// <Cmp {...props} />
// <footer>底部声明</footer>
// </>
// )
// }
// }
// const withLayout = Cmp => props => {
// return (
// <>
// <div style={
{ color: 'blue' }}>我是顶部导航</div>
// <Cmp {...props} />
// <footer>底部声明</footer>
// </>
// )
// }
// withLayout('张三')(Child)
// const withLayout = name => Cmp => props => {
// return (
// <>
// <div style={
{ color: 'blue' }}>我是顶部导航 -- {name}</div>
// <Cmp {...props} />
// <footer>底部声明</footer>
// </>
// )
// }
// 反向继承,可以用它机制对原来的render渲染时行劫持后处理
const withLayout = title => Cmp => {
return class Layout extends Cmp {
render() {
// 调用父组件中的render方法
const ele = React.cloneElement(super.render(), {
}, title)
return (
<>
<Cmp {
...this.props} />
{
ele}
</>
)
}
}
}
export default withLayout
在App.jsx文件
import React, { Component, Fragment } from 'react'
import withLayout from '@/hoc/withLayout'
// Fragment 组件,只占位不编译,可以用它来当顶层元素
// Fragment 还可以简写,简写就不需要引入
class Child extends Component {
render() {
console.log('child', this.props)
return (
<>
<h3>Child内容</h3>
</>
)
}
}
// const ChildCmp = withLayout(Child)
const ChildCmp = withLayout('张三')(Child)
class App extends Component {
render() {
return (
<>
<h1>App组件</h1>
<hr />
<ChildCmp a='aaa' b='bbb' c='ccc' />
</>
)
}
}
// class App extends Component {
// render() {
// return (
// <Fragment>
// <h1>App组件</h1>
// <h3>我是内容</h3>
// </Fragment>
// )
// }
// }
// export default withLayout(App)
export default App
高阶组件的反向继承
import React, {
Component, Fragment } from 'react'
import withLayout from '@/hoc/withLayout'
// Fragment 组件,只占位不编译,可以用它来当顶层元素
// Fragment 还可以简写,简写就不需要引入
class Child extends Component {
render() {
return <h3></h3>
}
}
const ChildCmp = withLayout('标题显示')(Child)
class App extends Component {
render() {
return (
<>
<h1>App组件</h1>
<hr />
<ChildCmp />
</>
)
}
}
export default App
memoization(记忆组件[计算属性])
连续两次相同传参,第二次会直接返回上次的结果,每次传参不一样,就直接调用函数返回新的结果,会丢失之前的记录,并不是完全记忆,可以在它的参数中传入state数据从而实现了类似Vue中的计算属性功能
安装
npm install memoize-one
类组件中需要安装,函数组件中内置了不用安装
import React, { Component } from 'react'
// 记忆组件:如果它的依赖项没有改变,那么就不会重新计算结果,而是使用上一次计算的结果
import memoizeOne from 'memoize-one'
class App extends Component {
state = {
n1: 1,
n2: 2
}
// sum = (n1, n2) => {
// console.log('sum被调用了')
// return n1 + n2
// }
// 回调函数中的n1和n2参数就是它的依赖项,只要它俩没有发生改变,下一次调用就会使用上一次的结果
sum = memoizeOne((n1, n2) => {
console.log('sum被调用了')
return n1 + n2
})
render() {
const { n1, n2 } = this.state
return (
<>
<h1>App组件</h1>
<div>{this.sum(n1, n2)}</div>
<div>{this.sum(n1, n2)}</div>
<div>{this.sum(n1, n2)}</div>
<hr />
<button
onClick={() =>
this.setState(state => ({
n1: state.n1 + 1
}))
}
>
n1+1
</button>
</>
)
}
}
export default App
portals
Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案
import React, { Component } from 'react'
// createPortal 把组件渲染到指定的DOM元素上
import { createPortal } from 'react-dom'
class Dialog extends Component {
mountNode = document.createElement('div')
componentDidMount() {
document.body.appendChild(this.mountNode)
}
componentWillUnmount() {
document.body.removeChild(this.mountNode)
}
render() {
// return createPortal(this.props.children, document.getElementById('dialog'))
if (!this.props.visable) return null
return createPortal(this.props.children, this.mountNode)
}
}
class Child extends Component {
render() {
return (
<div>
<h3>我是一个弹出层</h3>
<button
onClick={() => {
this.props.setIsShow(false)
}}
>
关闭弹出层
</button>
</div>
)
}
}
class App extends Component {
state = {
isShow: false,
visable: false
}
setIsShow = (isShow = true) => {
this.setState({
isShow: isShow
})
}
render() {
return (
<div>
<h1>App组件</h1>
<button
onClick={() => {
// this.setIsShow(true)
this.setState({
visable: true
})
}}
>
显示出来
</button>
<hr />
{/* <Dialog>{this.state.isShow ? <Child setIsShow={this.setIsShow} /> : null}</Dialog> */}
<Dialog visable={this.state.visable}>
<Child />
</Dialog>
</div>
)
}
}
export default App
- 装饰器
JavaScript 装饰器是一种以 @ 符号开头的特殊语法,放在目标代码的前面用于包装或扩展代码功能。JavaScript 的装饰器语法目前仍处于提案阶段,现阶段使用的话需要通过 bable 等方式进行编译之后,才能在浏览器正常运行。装饰器分为两种:类装饰器,类成员装饰器,分别用于装饰我们的类以及类的成员。
### 装饰器只能装饰类
// 定义一个类装饰器
const classDecorator = (target) => {
// do something
}
// 使用装饰器
@classDecorator
class Human {
}
# 支持装饰器
yarn add -D @babel/plugin-proposal-decorators@7
// craco.config.js配置文件,添加如下配置
// webpack的增量配置
module.exports = {
eslint: {
enable: false
},
babel: {
// 让当前的react工程支持js的装饰器
plugins: [['@babel/plugin-proposal-decorators', {
legacy: true }]]
}
}
import React, { Component } from 'react'
import withLayout from './hoc/withLayout'
// 类装饰器 它就是一个函数
// 装饰函数在给类装饰时,没有写小括号,则它的参数为当前装饰的类
// 可以给类添加成员属性或方法
// function handle(Target) {
// // 静态方法
// Target.xx = 'a'
// // 成员属性或方法
// Target.prototype.yy = 'b'
// Target.prototype.fn = function(){}
// }
@withLayout
class App extends Component {
render() {
return (
<div>
<h1>App</h1>
</div>
)
}
}
export default App
config.config.js
const path = require('path')
module.exports = {
eslint: {
enable: false
},
webpack: {
alias: {
// 将 @ 别名指向你的源代码目录
'@': path.resolve('src')
}
},
babel: {
// 让当前的react工程支持js的装饰器
// @babel/plugin-proposal-private-property-in-object 可装可不装
// yarn add -D @babel/plugin-proposal-decorators@7
plugins: [['@babel/plugin-proposal-decorators', {
legacy: true }]]
},
devServer: {
setupMiddlewares: (middlewares, {
app }) => {
app.get('/api/users', (req, res) => {
res.send({
code: 0,
msg: 'ok',
data: 1000
})
})
return middlewares
},
proxy: {
'/api': {
target: 'https://api.iynn.cn/film',
changeOrigin: true
// pathRewrite: { '^/api': '' }
}
}
}
}
四、css-in-js
- 介绍
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选择器会有一小段哈希值来保证全局唯一性来避免样式发生冲突。
- 安装
npm install styled-components
- css模块化
import React, { Component } from 'react'
// react它默认支持css模块的,对于css预处理器,需要安装包和配置
// sass支持只需要安装解析器就可以,不用配置,内置配置好了,别的预处理器需要配置
// react中样式默认是没有隔离的
// + 1. css模块化
// 1.1.css模块化它一定要以 xxx.module.css命名
// 1.2.引入 import appStyle from './style/app.module.css'
// + 2. css预处理器来编写样式
// 2.1.安装对应的loader
// 2.2.可以给顶层容器起一个唯一的名称,然后子元素中可以嵌套写样式
// 2.3.如果css预处理文件名称为xxx.module.scss,可以进行模块化引入
// + 3. css-in-js技术
// 3.1.把css通过js来解析输出,css写在js文件中
// 3.2.css-in-js它是一个技术,现在流行的库为styled-components
// import './style/app.css'
// import './style/child.css'
// class Child extends Component {
// render() {
// return (
// <div className='child'>
// <h3 className='title'>Child</h3>
// </div>
// )
// }
// }
// css模块化
import appStyle from './style/app.module.css'
import childStyle from './style/child.module.css'
// console.log(appStyle,childStyle)
class Child extends Component {
render() {
return (
<div>
<h3 className={childStyle.title}>Child</h3>
</div>
)
}
}
class App extends Component {
render() {
return (
<div>
<h1 className={appStyle.title}>App</h1>
<hr />
<Child />
</div>
)
}
}
export default App
在src目录下创建style文件夹,在里面创建app.css和app.module.css
app.css
/* h1 {
color: red;
} */
/* .title {
color: red;
} */
.active {
color: red;
}
app.module.css
.title {
color: red;
}
- scss预处理起解析
import React, { Component } from 'react'
// react它默认支持css模块的,对于css预处理器,需要安装包和配置
// sass支持只需要安装解析器就可以,不用配置,内置配置好了,别的预处理器需要配置
// react中样式默认是没有隔离的
// + 1. css模块化
// 1.1.css模块化它一定要以 xxx.module.css命名
// 1.2.引入 import appStyle from './style/app.module.css'
// + 2. css预处理器来编写样式
// 2.1.安装对应的loader yarn add -D sass
// 2.2.可以给顶层容器起一个唯一的名称,然后子元素中可以嵌套写样式
// 2.3.如果css预处理文件名称为xxx.module.scss,可以进行模块化引入
// + 3. css-in-js技术
// 3.1.把css通过js来解析输出,css写在js文件中
// 3.2.css-in-js它是一个技术,现在流行的库为styled-components
// 可以给它的顶层容器起一个唯一的名称,然后子元素中可以嵌套写样式
import './style/app.scss'
import './style/child.scss'
class Child extends Component {
render() {
return (
<div className='child'>
<h3 className='title'>Child</h3>
</div>
)
}
}
class App extends Component {
render() {
return (
<div className='app'>
<h1 className='title'>App</h1>
<hr />
<Child />
</div>
)
}
}
export default App
在style目录下创建app.scss和child.scss
app.scss
.app {
.title {
color: red;
}
}
child.scss
.child {
.title {
color: blue;
}
}
- components
import React, { Component } from 'react'
import { AppWrapper, BtnWarpper, BtnPrimaryWarpper, TitleWrapper } from './style/style'
class App extends Component {
render() {
return (
<AppWrapper>
<h1 className='title'>App</h1>
<hr />
<BtnWarpper>我是一个按钮</BtnWarpper>
<br />
<br />
<BtnPrimaryWarpper>我是一个按钮</BtnPrimaryWarpper>
<hr />
<TitleWrapper color='blue' size='50'>
我是一个标题
</TitleWrapper>
</AppWrapper>
)
}
}
export default App
// 通过js来定义样式
import styled from 'styled-components'
export const AppWrapper = styled.div`
.title {
color: blue;
}
`
export const ChildWrapper = styled.div`
// 里面的写法和css预处理器一样
background-color: #f00;
height: 200px;
.title {
color: green;
font-size: 50px;
}
`
export const BtnWarpper = styled.button`
height: 60px;
line-height: 60px;
width: 200px;
`
// 样式继承
export const BtnPrimaryWarpper = styled(BtnWarpper)`
border: none;
color: white;
background-color: blue;
`
// 属性传递
const weight = 'bold'
export const TitleWrapper = styled.div`
color: ${
props => props.color || 'red'};
font-size: ${
props => props.size || 16}px;
font-weight: weight;
`
css模块化和scss和components推荐使用scss
五、补充
因为学习周期过长,我后面设置一个React学习的专栏,方便学习和管理笔记