;这种方式会定义很多的对象,虽然可以模块的形式导出,但样式的抽取、打包不方便
- 方式三:
1. 在 css 目录下定义样式文件,文件命名要和组件有一定的关联,以便于查找
2. 在 webpack.config.js(或 webpack.pub.config.js)中为 css-loader 配置开启模块化(modules:true;),并重定义类名的规范(localIdentName:'[local]--[hash:base64:5]')
3. 在需要样式文件的 jsx 文件中,import ObjStyle form '样式文件路径';
4. 将类名修正。例如:原来是\
\<\/div\>
## 六、React 组件的生命周期
### React@16.3开始的版本,生命周期钩子的原来的名字要使用,前面需要加一个 UNSAFE\_前缀(举例:componentWillUpdate,应该写作 UNSAFE_componentWillUpdate),要不然控制台就会提示警告信息
### 在 React 属性,有一些属性必须传的。但是没有传,就会从默认属性 defaultProps 中获取这个【启动参数】
- 启动参数以静态变量的方式,在 class 类组件中定义;例如:static defaultProps={initVal:0}
### 对父组件或其他组件传递过来的属性,进行必要的类型校验,以确保当前组件拿到的是需要的数据类型
- react@15.x版本以后,类型校验需要的包已经被单独封装成了一个包,prop-types,安装改包:$ npm i prop-types@15.7.2 --save-dev
- 在需要的组件中,导入一个对象;import ReactTypes from 'prop-types';
- 在类组件中同样定义一个静态变量,static propTypes={initVal:ReactTypes.number};
### 钩子 componentWillMount
- 含义:该阶段组件还没有开始挂载,也没有开始渲染虚拟 dom
- 整个生命周期阶段,只执行一次;可以调用 setState()方法,另外两个可以调用 setState 的钩子时 componentDidMount 和 componentWillReceiveProps
- 可以拿到 this.state、this.props 中的数据
- 可以调用自定义的函数
- 不能够操作 dom。原因;虚拟 dom 都还没有开始渲染,如果获取虚拟 dom 对象(document.getElementById('myVal')),结果是 null
### render 函数渲染阶段
- 含义:开始渲染虚拟 dom,render 函数执行完成,虚拟 dom 就渲染完成了,生成了一个虚拟 dom 树
- 可以拿到数据,前面能够拿到的数据,也一样能拿到
- 在 return 语句之前,获取虚拟 dom,仍然是 null
- render 函数调用次数是:大于等于 1 次。如果组件从渲染到销毁,没有触发属性或数据变化,就执行一次。一旦出现上述数据的变化,render 函数的执行次数,必然大于等于 2 次
- 注意:render 函数在组件创建阶段和组件运行阶段都有调用,而且调用的是同一个 render 函数,因此 render 函数的调用次数,是组件创建阶段和组件运行阶段的 render 调用次数,一起计算
- render 阶段获取父组件或其他组件传递过来的属性值,直接使用 this.props.属性名就可以获取。这个 props 是固定写法,不依赖当前组件是否显式定义了构造函数,是否在构造函数中传递了参数
### 钩子 componentDidMount
- 含义:到该阶段虚拟 dom 已经创建完成,数据、虚拟 dom 和页面三者保持一致,数据已经渲染到页面上了
- 该钩子在整个生命周期中,只执行一次。在钩子内部可以调用 setState 方法
### 钩子 shouldComponentUpdate,有两个参数
- 组件是否需要更新,需要返回一个布尔值。true 表示组件需要在页面上更新,值为 false 组件不会在页面上更新了,该钩子后面的生命周期钩子也不会执行了
- 该钩子有两个参数 shouldComponentUpdate(nextProps,nextState),nextProps 中返回的是旧的属性值,nextState 中返回的是更新后的 state 对象值
### 钩子 componentWillUpdate,有两个参数
- 含义:组件即将重新挂载,还没有开始成重新渲染虚拟 dom
- 两个参数:nextProps 返回的是当前组件的属性变化前的对象,nextState 是数据更新后新的 state 对象,同 shouldComponentUpdate 中一样
### 钩子 render 函数
- 已经在前面做了说明
### 钩子 componentDidUpdate,有两个参数,prevProps,prevState;特殊:这两个参数都是变化前的对象,与前面一旧一新不同
- 含义:该阶段更新的数据、重新渲染的虚拟 dom 和更新的页面保持一致,改变的数据已经重新渲染到页面中了
- prevProps 指 state 变化前传递的属性对象,prevState 指更新前的 state 对象
### 钩子 componentWillUnmount
- 含义:组件销毁阶段
### 钩子 componentWillReceiveProps,有一个参数,nextProps
- 父组件传递给子组件属性,只有当父组件中传递的这个值变化时,才触发这个钩子,结合 TestReceiveProps.jsx 案例理解
- nextProps 中返回的是传递过来的变化后的对象,当前子组件的 this.props 打印的还是传递的旧对象
## 七、bind 绑定 this 参数并传值的三种方式
### 结合案例 ThisBind.jsx
### 三种方式
#### 1.在 render 函数调用处绑定
- 绑定处,onClick={this.firstHandle.bind(this[,arg1,arg2])}
- 接收处,事件处理函数写成普通函数就可以了
#### 2.在构造函数中绑定并赋值
- 绑定处, constructor(props){super(props); this.secondHandle=this.secondHandle.bind(this,arg1,arg2);}
- 接收处,secondHandle 写成普通函数就可以了
#### 3.使用箭头函数-要解决解析到调用处即执行的问题
- 绑定处,onClick={()=>this.secondHandle(arg1,arg1)};注意不能写成 onClick={this.thirdHandle(arg1,arg2)}这样编译器解析到此处时就会发现 this.thirdHandle()是一个箭头函数,而且立即调用。这不是预期的结果,onClick 事件的目的是人为地来触发,而不是让系统解析代码时触发
- 接收处,thirdHandle=(arg1,arg2)=>{console.log(this)}; 接收处写成箭头函数
### 辅助知识-bind 的三种用法,结合案例 bindUse.js 理解
#### 1.创建绑定函数,react 中前两种都是使用 bind 绑定 this 并传参
#### 2.偏函数,基本用途是为函数预设一个初始参数
#### 3.定时器修改 this 指向,setTimeout(function(){}.bind(this),1000)
## 八、子组件向父组件传值的两种方式
### 结合案例[父子组件之间的传值](https://blog.csdn.net/weixin_42881744/article/details/105706084)
### 父传给子组件
- 不用赘述,绑定属性,这个属性可以是变量,表达式值(如 initVal={0},也可以是函数名)
### 子组件向父组件传值的两种方式
## 九、Context 特性的使用方法
### 使用场景
- 祖父级组件给孙子组件传递一个属性,常规情况下,需要层层传递。祖父传给父组件,父组件才传递给子组件,这种方式将不需要接收数据的父组件也牵涉其中。更何苦,如果层级更深,这种传递灵活性低,而且代码冗余
- 为此,React 引入了 Context 特性来解决这个问题
### 使用步骤
1. 在需要发送数据的顶层组件,定义一个方法,getChildContext,该方法有返回值,返回一个对象,对象的键是要传递的属性的键。如:eturn {fontSize:this.state.fontSize}
2. 同样在该顶层组件中,进行一个数据校验,对要传递出去的数据进行校验,定义一个静态变量 static childContextTypes=fontSize:ReactTypes.number}
3. 在接收处组件,同样地对接收的数据进行类型校验,此时要定义的检验变量是 contextTypes。如:static contextTypes=fontSize:ReactTypes.number}
4. 在 render 函数或者其他位置,使用 this.context.键名来获取值。例如:this.context.fontSize
### 记忆方式
- getChildContextTypes,前三、后三、后二
- 一个方法,两个静态属性
- 在发送数据处,前三、后三,即:getChildContext 方法,childContextTypes 类型校验
- 在接收数据处,后二,contextTypes
- 然后,获取值使用,this.context.[键名]
## 十、项目问题汇总
### 问题 1
- ant 框架,提供的 Layout 布局中,点击不同的路由可以实现切换,选中的路由背景为深蓝色的。但是,当手动刷新一下页面后,路由地址栏没有发生变化,但是选中的路由深蓝色背景消失,而【首页】上显示深蓝色背景,切换到\/movie 和\/about 都出现这个问题。
- 原因是:确定哪个路由选中,是有 Menu 标签中 defaultSelectedKeys="['1']"控制的。手动刷新页面后,App 组件重新创建,Menu.Item 中的 key 值销毁,无法和 defaultSelectedKeys 建立关联
### 解决
- 借助 BOM 中的 location 对象,window.location 能够在组件 App 创建阶段拿到当前,手动刷新页面后保持不变的路由 hash 值。将该值跟踪 defaultSelectedKeys 中的值。defaultSelectedKeys={window.location.hash.slice(2)}。另外一种方式是,在钩子 componentWillMount 中使用 this.setState()方法,改变挂载在 state 对象上的值
### 问题 2-编程式重定向的好处
- 重点-编程式重定向推荐使用,区别于声明式重定向在构造函数中,直接使用 this.props.history.push()比在 App.jsx 中使用\
这种方法的优点是,地址栏也立即变化。而在 Switch 标签中嵌入 Redirect(单标签),初次时,地址栏是不变化的。只有点击到它的子路由后,地址栏才变成"/movie/in_theaters/1"
### 解决
#### 使用编程式重定向替代声明式,具体做法是在要重定向的那个组件的构造函数,添加 props.history.push(重定向路径)
- 写法 1:联想 this.state 私有数据,this.props.history.push('/movie/in_theaters/1');
- 写法 2:props 是在构造函数中的参数,this 可以省略
### 问题 3-控制台弹出
- 控制台报错:Can't perform a React state update on an unmounted component。组件已经销毁,但是还有未结束的异步任务,使用下列语句取消异步方法
### 解决
#### 解决方案:在组件销毁钩子中添加 this.setState 语句
- UNSAFE_componentWillUnmount() {
this.setState = () => false;
}
## 问题 4-路由导航与 this.props 属性的关系
### 场景
- 在当前组件-PerMv 中,有一个点击.box,编程式导航的操作。点击.box,导航至“详情页”,然而会报一个错误:PerMv.jsx:124 Uncaught TypeError: \'Cannot read properties of undefined reading push\'
- 原因:不能读取属性 push,问题出在 this.props 中没有 history 属性;因此 this.props.history 就是 undefined,后面再调用 push 方法是不可能实现的
### 解决方案
##### 方案一
须知 PerMv 是 SubMovie 的子组件,在 SubMovie 组件中循环渲染 PerMv 时,可以为 PerMv 绑定一个 history 属性或者直接使用属性扩散将
this.props 传递
1.1 \\<\/PerMv\>
1.2 编程式导航中只用到 history,绑定属性 this.props 中的 history 也是可以的
\\<\/PerMv\>
##### 方案二
使用 withRouter 插件,它来自 react-router-dom 这个包,和 Route Link Switch 等路由相关标签一样,按需导入一下即可
2.1 引入,import {withRouter} from \'react-router-dom\';
2.2 在 PerMv 组件中,使用 export default withRouter(PerMv);导出当前组件使用 withRouter(PerMv)调用后的组件
## 问题 5-Link 和 Route 中 to 和 path 属性的设定问题
### 场景和解决方案
- 电影\/movie 路径下,有三个子路由/movie/in_theaters/id,/movie/coming_soon/id,/movie/top250/id。为实现切换到一级路由【电影】时,立即选中的是二级子路由的第 1 个(即:/movie/in_theaters/id)。只需要将 Link 中 to="/movie"改成 to="/mo vie/in_theaters/1"即可,to 的含义:是浏览器地址栏的去向,而组件和路由匹配,要达到的目标是尽可能的复用,以减少代码的复杂度。而 Route 在 React 中有两重功能:一、占位符,类似 vue 中的 router-view。而匹配规则:当/movie 下要设定多个子路由时,该 Route 中的 path 应该能匹配到一级路由/movie 和其他三个二级子路由,里面的 path 正确写法为:\\<\/\>
- 体会 React 中 Link 中 to 和 Route 中 path 的区别
## 问题 6-手动刷新触发其路由对应的组件的上层组件全部从 0 开始创建
### 为什么 App.jsx 首次通过 window.location.hash 能够打印/home?而切换至电影和关于却无法打印相关路由
1. 页面路由从/重定向到/home,此时也是 App.jsx 组件首次创建,App 组件经历创建阶段,当前钩子 componentWillMount 自然会执行,于是
window.location.hash 按照预期打印出来
2. 选择路由按钮,切换【电影】和【关于】的路由,此时会切换至 App 的子组件 Movie 或者 About 组件,但是 App 组件并没有重新渲染,因此
componentWillMount 没有执行,所有正常切换至其他路由不会在当前钩子中打印出 hash 值
3. 比如:切换至 movie 的路由,/movie/in_theaters/1,手动刷新一次页面,此时 App 组件就开始重新基于当前路由(/movie/in_theaters/1)开始渲染新的 App 组件,当前生命周期钩子 componentWillMount 中能打印出此时的路由
4. 第二级别的路由也是同样地情况,手动刷新/movie/in_theaters/1 和/movie/coming_soon/1、/movie/top250/1 都会基于当前地址重新创建 Movie 组件。也是同样的结果,最初 Movie.jsx 中 componentWillMount 只能拿到最开始这个路由,正常切换当前钩子不会打印即时的路 由。手动刷新触发 Movie 组件重新创建了,才会打印即时的路由;总之,深入理解虚拟 dom 的 diff 算法,总会智能地计算出更新 dom 的最小代价。手动刷新将触发所有组件路由 Route 所在组件从 0 开始的创建;观察手动刷新以后,从 App 开始的各级别组件,window.location.hash 都能拿到即时值
## 问题 7
### 场景-请求数据接口的时机
#### /movie 三个子路由切换时
- 4.1 首先,在 componentWillMount 中调用一次 fetch 异步请求数据,完成 SubMovie 界面的初始化渲染
- 4.2 其次,使用 compontWillReceiveProps(nextProps)中,this.setState 重置 state 中相关数据,然后在回调中请求后台数据,基于新数据更新页面
- 伪代码
- componentWillReceiveProps(nextProps){
- this.setState({
- // 基于 nextProps 更改请求参数
- },()=>{
- // 回调中,向后台发起请求
- })
- }
#### 从电影列表中,点击某一部电影显示块,编程式导航,进入 Detail 组件
- 分析:这里匹配的路由从/movie/:type/:page 到/movie/detail/:id,对应的组件从 SubMovie 变成了 Detail,无论是任何一部电影,【进入详情】这一个过程,始终伴随着新组件的从零开始创建,因此在 componentWillMount 中请求后台数据,这是一个异步过程,执行到异步任务时,先把异步任务推送到队列中,继续向下执行,完成【加载中……】动画的渲染。而后,后台数据也请求过来了,this.setState({isLoading:false,detail:res})来触发虚拟 dom 的重新更新;然而/movie 的三个子路由,则匹配到的是同一个 SubMovie,组件切换时,通过 componentWillReceiveProps(nextProps)的函数体中使用 this.setState 重新更新虚拟 dom
## 问题 8
### 和属性有关的方法
#### Object.keys(obj)
- 含义:将 obj 对象自身的可枚举属性的一个或多个键,以数组的形式返回
#### Object.getOwnPropertyNames(obj)
- 含义:将 obj 对象自身的属性包括可枚举和不可枚举属性的键,以数组的形式返回
#### obj.hasOwnProperty(key)
- 含义:判断对象 obj 上是否有 key 属性(只判断有无,不管他是否可枚举),不会检查原型上是否有 key 属性;
返回值是布尔类型。有返回 true;无,返回 false
#### obj.propertyIsEnumerable(key)
- 含义:判断 obj 上的 key 属性是否可枚举,和 hasOwnProperty 一样,不会检查原型上的属性。即时,原型上的某个属性是 key 枚举,通过这个方法验证,也会返回 false
#### for---in 循环
- 含义:for---in 循环可以遍历数组上的对象自身和其原型上的所有【可枚举属性】
- 注意:上述五个知识点中,只有 for---in 循环涉及到了原型上的属性,可以通过 obj.hasOwnProperty(key)这个方法过滤掉原型上的属性
## 问题 9
### 场景
- babel-plugin-import 插件,在.babelrc 中配置了,按需导入。打包时,图标的 js 文件 dist.js 文件仍然被打包进去了(webpack 打包可视化分析插件,webpack-bundle-analyzer),导致打包体积过大
### 解决办法
- 在配置文件 webpack.pub.conifg.js 文件中,添加一级节点 resolve,然后起别名 alias 对象中,配置路径映射。将"@ant-design/icons/lib/dist"路径映射到自定义文件路径,定义在了 src/utils/icons.js 文件中
- 这个自定义文件导出了,组件所依赖的基本对象,主要包括两大类:框线和实底风格;框线的有 5 个,在@ant-design/……/outline 文件夹下,包括 CloseOutline、UpOutline、DownOutline、LeftOutline、RightOutline;实底的有两个:在@ant-design/……/fill 文件夹下,包括 CheckCircleFill 和 CloseCircleFill
## 问题 10
### 场景
- 为开发好的页面添加一个跟随路由切换的标题
### 解决方案
#### 方案一:使用 history 的 listen 监听和生命周期钩子中处理 window.location.hash
1. 在 App.jsx 的构造函数中监听路由的值,并使用 document.title 设置标题;在此之前,要主要观察 App.jsx 组件中是否有 HashRouter,如果有最好将其移至 ReactDom.render(\\\<\/App\>\<\/HashRouter\>, document.getElementById('app'))中声明。原因是:要在 App.jsx 的 this.props 属性中,获取不到 history 对象。而我们直到,高阶组件 withRouter 可以帮我们实现这个目的;然后,在构造函数中使用 props.history.listen(cb); cb=(location)=>{// location.pathname};
2. 还有个小 bug:当手动刷新页面时,监听函数 listen 不再有效了。手动刷新,当前组件肯定是从零开始重建,那么 componentWillMount 钩子肯定执行;window.location.hash 可以拿到刷新后的当前路由的 hash 值,处理 hash 值,来 document.title 设置 title 标题;
3. 优化:可以封装一个成员函数,来处理不同的路由地址对应的 title 标题。addTitle(cur_path),cur_path 的值在正常切换时,listen 监听起作用,cur_path 传入的是 location.pathname;手动刷新时,cur_path 传入的是 window.location.hash.slice(1),hash 值多一个#号
#### 方案二:使用插件 react-document-title 来实现,引入后,成为一个标签,加入到各路由对应的组件的顶层,并设置一个 title 值即可
#### 方案三:使用插件 react-helmet,更灵活,可以修改主页 index.html 的元信息,如:meta、link 和 title 等
## 问题 11
### 场景
- 浏览器顶部进度条
### 解决方案
- npm i npprogress --save-dev
- npm install --save hoist-non-react-statics,[hɔɪst]升起,起重,吊起;hoist-non-react-statics 插件作用类似 Object.assign。将子组件的静态方法拷贝到另一个组件上,但会屏蔽 react 关键字的静态成员,以免他们被覆盖
- [具体代码,参考](https://blog.csdn.net/m0_37890289/article/details/109739783?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ETopBlog-1.topblog&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ETopBlog-1.topblog&utm_relevant_index=1)
#### 如果使用了 axios 请求数据,则使用拦截器(请求拦截器和响应拦截器)
- 请求拦截器
- const instance=axios.create({timeout:5000});
- instance.interceptors.request.use((config)=>{
- NProgress.start();
- return config;
- },function(err){
- return Promise.reject('error');
- })
- 下面是响应拦截器
- instance.interceptors.response.use((response)=>{
- NProgress.done();
- return response;
- },function(err){
- return Promise.reject('err);
- })
## 问题 12
## 场景
- web 网页中播放视频数据流
### 解决方案
- video-react
- 知乎视频播放器 Griffith-[文档](https://www.open-open.com/project/5098117654808693719.html)
- react-player,推荐使用;比较通用。如果传入 youtube 网址(如: url: \'https://www.youtube.com/watch?v=rMVAqU8fmio\'),视频播放器的外观直接就是 youtube 官方的外观;如果传入的是普通文件路径 ReactPlayer 的 url={[{src:\'a.ogg\',type:\'video/ogg\'}]}或者直接 url={[\'video\/fans.mp4\']},则显示通用的外观
## 问题 13
### 场景
- 打包好的文件,使用 live-server 打开时,会报一个错误"Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('https://xxx.cn') does n"
- 控制台提示:Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('') does not match the recipient window's origin ('').
### 原因
- 引入的 react-player 插件,使用的是
- [参考文档:](https://blog.csdn.net/weixin_45045689/article/details/119571960?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-2.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-2.pc_relevant_default)
- [参考文档:Vue 进阶(九十二):应用 postMessage 实现窗口通信](https://shq5785.blog.csdn.net/article/details/104042541?spm=1001.2101.3001.6650.3&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-3.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-3.pc_relevant_default)
- [参考文档:Vue 进阶(八十六):iframe 结合 window.postMessage 实现跨域通信](https://blog.csdn.net/sunhuaqiang1/article/details/103281406)
- [参考文档:两个跨域页面进行跳转传参的终极方案](https://www.jianshu.com/p/6be6c3f2867a)
- [vue 中使用 iframe+postMessage 进行跨项目的通信](https://blog.csdn.net/weixin_44022194/article/details/107959725?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1.pc_relevant_antiscanv2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1.pc_relevant_antiscanv2&utm_relevant_index=2)
### 解决方案
#### 方案一,不起作用
-
-
-
-
- 在 index.html 文件中声明 target orgin 是www.youtube.com,语法如上所示,将目标源路径赋值给,不包括协议名称https,document.domain赋值
- [document.domain 跨域参考文档](https://segmentfault.com/a/1190000005863659)
#### 方案二,试着从 react-player 着手
## 问题 13
### 问题描述
- ERROR in ./src/images/logo.pn Module build failed (from ./node_modules/image-webpack-loader/index.js):
Error: Cannot find module 'imagemin-gifsicl
### 解决方案
- nrm ls
- nrm use cnpm
- 使用cnpm安装包 "image-webpack-loader": "^8.1.0"