初始化React脚手架
前期准备
- 1.脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目
- 1.包含了所有必要的配置(语法检查、jsx编译、devServer…)
- 2.下载好了所有干系的依靠
- 3.可以直接运行一个简单效果
- 2.react提供了一个用于创建react项目标脚手架库: create-react-app
- 3.项目标团体技术架构为: react + webpack + es6 + eslint
- 4.使用脚手架开辟的项目标特点: 模块化, 组件化, 工程化
全局安装工具
首先要在全局安装create-react-app,保举使用yarn来安装,因为react和yarn都是facebook出的。但是大部分人用的npm,也是完全可以,这里用npm演示
全局安装react脚手架工具:
- npm i -g create-react-app
复制代码 i代表install,g代表global全局,create-react-app代表react脚手架
安装后查看版本:
- D:\WebProject\ReactProject>npm create-react-app -v
- 6.14.16
复制代码 我们依靠这个就可以在想要的目录下面创建react工程
安装脚手架
切换到想要创建脚手架的目录
使用命令:create-react-app 工程名字 名字仅限英文,不要用特殊字符
安装完毕之后,可以看到React提示的四个命令:
- npm start 启动项目
- Starts the development server.
- npm run build 打包
- Bundles the app into static files for production.
- npm test 测试模式启动
- Starts the test runner.
- npm run eject 将隐藏的webpack配置文件显示出来
- Removes this tool and copies build dependencies, configuration files
- and scripts into the app directory. If you do this, you can’t go back!
复制代码
我们可以查看配置文件,package.json,所有启动短命令已经配置好了
输入npm start,启动项目
脚手架布局
这种项目也叫SPA项目:
- public ---- **静态资源文件夹**
- favicon.icon ------ 网站页签图标
- index.html -------- **主页面,整个项目只有这一个html文件,作为基点**
- logo192.png ------- logo图
- logo512.png ------- logo图
- manifest.json ----- 应用加壳的配置文件
- robots.txt -------- 爬虫协议文件
- src ---- **源码文件夹**
- App.css -------- App组件的样式
- App.js --------- App组件
- App.test.js ---- 用于给App做测试
- index.css ------ 样式
- index.js ------- 入口文件
- logo.svg ------- logo图
- reportWebVitals.js
- --- 页面性能分析文件(需要web-vitals库的支持)
- setupTests.js
- ---- 组件单元测试的文件(需要jest-dom库的支持)
复制代码 启动项目后默认页面
详解index.html
作为整个项目里唯一一个html文件,也是整个app的基点,有必要具体解读一下
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="utf-8" />
- <!-- %PUBLIC_URL%代表public文件夹的路径 -->
- <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
- <!-- 开启理想视口,移动端适配 -->
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <!-- 配置浏览器页签颜色(仅支持安卓原生浏览器) -->
- <meta name="theme-color" content="#000000" />
- <!-- 描述网站信息的,搜索引擎会读取这个 -->
- <meta
- name="description"
- content="Web site created using create-react-app"
- />
- <!-- 苹果手机的Safari把网页添加到主屏幕后,启动图标 -->
- <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
- <!-- 将web套壳成为安卓或ios的安装包用,(应用加壳技术) -->
- <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
- <!-- 网页tab的名字 -->
- <title>React App</title>
- </head>
- <body>
- <!-- 如果客户端浏览器不支持JS的运行 -->
- <noscript>You need to enable JavaScript to run this app.</noscript>
- <!-- 根标签 -->
- <div id="root"></div>
- </body>
- </html>
复制代码
src目录剖析
App组件干系
webpack入口干系:index.js
假如是用webpack打包,那么默认情况下,index.js就是入口文件
- const root = ReactDOM.createRoot(document.getElementById('root'));
- root.render(
- <React.StrictMode>
- <App />
- </React.StrictMode>
- );
- **上下这两种写法是完全等价的**
- ReactDOM.render(<App />,document.getElementById('root'))
复制代码 <React.StrictMode>
- 这个<React.StrictMode>并不是ES5的严格模式,而是React检查子组件是否有不合理的地方
- 比如:ref=字符串,或者一些过时的写法,来做一些检查
- <React.StrictMode>
- <App />
- </React.StrictMode>
复制代码 reportWebVitals()
作为入口的最底下的方法,是用来记载网页的性能检测
至于public里的index.html是如何被src的index.js找到的,这个我们不必要关心,是React用webpack配置好的。所以index.html的名字是不可以任意动的,动了就找不到了。
我们写一个小应用,只必要去关注,index.html 主页面,App.js App组件,index.js 入口文件,这三个就够了。
注意:工程里import 进js和jsx都是不必要写后缀的
写一个小demo
1.我们保持public中的Index.html不变
2.修改src下面的APP.js以及index.js文件
App.js: 【注意:创建好的组件肯定要袒暴露去】
- //创建外壳组件APP
- import React from 'react'
- class App extends React.Component{
- render(){
- return (
- <div>Hello world</div>
- )
- }
- }
- export default App
复制代码 index.js: 【主要的作用其实就是将App这个组件渲染到页面上】
- //引入核心库
- import React from 'react'
- import ReactDOM from 'react-dom'
- //引入组件
- import App from './App'
- ReactDOM.render(<App />,document.getElementById("root"))
复制代码 如许在重新启动应用,就成功了。
我们也不建议如许直接将内容放入App组件中,只管还是用内部组件。
我们在顶一个Hello组件:
- import React,{Componet} from 'react'
- export default class Hello extends Componet{
- render() {
- return (
- <h1>Hello</h1>
- )
- }
- }
复制代码 在App组件中,进使用用
- class App extends Component{
- render(){
- return (
- <div>
- <Hello />
- </div>
- )
- }
- }
复制代码 如许的效果和前面是一样的。
但是由于普通的Js和组件都是js,所一最好组件使用jsx去展示。
样式模块化
前面会有一个问题,当css和组件越来越多的时候,假如css不分层不分级,就会造成杂乱的场景,同时带来样式冲突难以管理。
当组件渐渐增多起来的时候,我们发现,组件的样式也是越来越丰富,如许就很有可能产生两个组件中样式名称有可能会冲突,如许会根据引入App这个组件的先后顺序,后面的会覆盖前面的。
同名造成的冲突
传统的引入方式:
修改后的引入方式:
为了避免如许的样式冲突,我们采用下面的形式:
1.将css文件名修改: hello.css — >hello.module.css
css内部不消做出修改,也就是以对象形式举行css导入
注意:不要用{}套css对象,这玩意只是个标记,不是解构出来的!!!
会假如套了会导致对象获取不到!!!
2.引入并使用的时候改变方式:
- import React from "react";
- //引入css,此时css都以对象形式存在hello中
- import hello from "./hello.module.css"
- export default class Hello extends React.Component{
- render(){
- return(
- // 以对象形式获取css
- <div className={hello.title}>Hello world</div>
- )
- }
- }
复制代码 逆天导入方法
有些项目为了避免大量导入import时导致后面有一串路径,就采用这种简写方式,即每个文件夹下组件名和css名字都是 index的名字,这种情况下,导入的路径写到包就可以了,因为假如不写末了的文件名,默认就导入index
就会从这种具体到文件的导入
- import Hello from './component/Hello/hello.jsx'
- import Welcome from './component/Welcome/welcome.jsx'
复制代码 变成这种,导入截止到包名,后缀默认用index
- import Hello from './component/Hello'
- import Welcome from './component/Welcome'
复制代码 这种导入方法肯定是不保举的,但是有的公司可能就会用到这种,所以要提一下
React插件保举
可以快速帮助你创建模板
好比输入rcc,主动根据文件名创建类组件
rcc:react class component
输入rfc,主动根据文件名创建函数式组件
rfc:react function component
回车就主动天生类组件了,函数式组件也是一样
等等一些其他的快捷键可以自己去查,非常方便
组件化编码的流程
- 拆分组件: 拆分界面,抽取组件 (页面拆组件的过程中假如拆完了不知道叫啥名字,多半是没拆好)
- 实现静态组件: 使用组件实现静态页面效果
- 实现动态组件
3.1 动态表现初始化数据(数据驱动页面变革)
3.1.1 数据类型
3.1.2 数据名称
3.1.2 生存在哪个组件?
3.2 交互(从绑定事件监听开始)
脚手架插件
nanoId
相比于uuid更加轻量化,天生一个唯一id
UUID:天生唯一时间戳的库
npm i uuid (库比较大)
npm i nanoid(库小,很快安装)/ yarn add nanoid
- 用法:
- import {nanoid} from 'nanoid'
- //调用,直接就可以生成一个随机ID
- nanoid()
复制代码
PropTypes
脚手架本身并不自带这个,必要自行安装,完成类型的约束
用npm举行安装,安装后即可举行操作,对传入参数举行类型约束
- static propTypes = {
- addTodo: proptypes.func.isRequired,
- };
复制代码 TodoList组件Demo总结
拆分组件、实现静态组件,注意:className、style的写法
动态初始化列表,如何确定将数据放在哪个组件的state中?
- 1)某个组件使用:放在其自身的state中
- 2)某些组件使用:放在他们共同的父组件state中(官方称此操作为:状态提拔)
关于父子之间通信:
- 1)【父组件】给【子组件】传递数据:通过props传递
- 2)【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数
注意defaultChecked 和 checked的区别,类似的尚有:defaultValue 和 value
状态在那里,操作状态的方法就在那里
兄弟之间的组件通信,用Pub-Sub订阅机制实现,后续会有
脚手架集成Axios
Ajax和Axios是啥就不多说了
React本身并不带这些插件,所以必要额外去引入这些插件
前期安装插件
Axios官网
npm安装Axios npm install axios
npm查看版本 npm list axios
查看package里的依靠
JSX内导入
安装完成后就可以使用这种方式在JSX内导入
- import axios from "axios";
复制代码 方法内里发起Get请求,其他的用法可以看官网:Axios请求官网例子
- startAxios = () => {
- // 这里会先看自身有没有这个路径下的资源,没有的话才会去转发给代理服务器
- axios.get("http://localhost:3000/api/test").then(
- (response) => {
- console.log("成功了", response.data);
- },
- (error) => {
- console.log("失败了", error);
- }
- );
- };
复制代码 请求发起后发生跨域问题
这个时候就必要代理来办理跨域问题,跨域为什么要用代理是什么请自行搜索
请求实际上是已经到了server,但是Ajax引擎不许跨域的值返回,所以跨域给拦下来的本质是Ajax引擎把响应给拦住了
代理服务器与server是同一个域名+端口上的
只完成一个转发功能,代理服务器把请求转到Server上,Server的内容还给代理,代理上因为没有Ajax引擎,就把返返来的数据还给Client,由于代理与Client同源,没有跨域限制,也就办理了跨域问题
代理配置
package.json配置
可以叫做全局代理,因为它直接将代理配置在了配置文件 package.json 中
- "proxy":"http://localhost:5000"
- // "proxy":"请求的地址"
复制代码 追加的配置文件
如许配置代理时,首先会在在原请求地址http://localhost:3000/api/test上访问,假如访问不到资源,就会转发到这里配置的地址上去请求,也就是http://localhost:5000/api/test,假如这时候还没有,就404
说明:
- 优点:配置简单,前端请求资源时可以不加任何前缀。
- 缺点:不能配置多个代理。
- 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
修改了配置类的操作肯定要重启!!!
setupProxy.js配置
这里不消记,会查会配即可,但是要注意 http-proxy-middleware 与脚手架的版本兼容
肯定在src目录下!!!
- 第一步:创建代理配置文件
- 在src下创建配置文件:src/setupProxy.js
复制代码 - 编写setupProxy.js配置具体代理规则:
- const proxy = require('http-proxy-middleware')
- module.exports = function(app) {
- app.use(
- proxy('/api1', { //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
- target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
- changeOrigin: true, //控制服务器接收到的请求头中host字段的值
- /*
- changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
- changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
- changeOrigin默认值为false,但我们一般将changeOrigin值设为true
- */
- pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
- }),
- proxy('/api2', {
- target: 'http://localhost:5001',
- changeOrigin: true,
- pathRewrite: {'^/api2': ''}
- })
- )
- }
复制代码 两个关键配置属性详解:
changeOrigin:
控礼服务器收到的请求头中Host的值(本次请求从哪来的)
true:Origin是走代理的,false:Origin不走代理,是走代理前的host 。
一样平常都是true,避免后端做奇奇怪怪的check
- changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
- changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
- changeOrigin默认值为false,但我们一般将changeOrigin值设为true
复制代码 pathRewrite:
- 重写请求路径(必须) { "要被替换的路径": "替换成啥" },这里的规则是去掉了 /api1
- 相当于去掉了 /api1 对于后端来说不会有api1的前缀,这只是前端区分请求走哪个代理的标识,真正发起请求给后端要把这种标识干掉
- 当然,这种做法也不绝对,也可以把重写的路径换成需要的东西,灵活配置即可
复制代码 说明:
- 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
- 缺点:配置繁琐,前端请求资源时必须加前缀。
setupProxy.js配置过程中遇到的坑
参考文档
因为const proxy = require("http-proxy-middleware");这个中间件的版本不兼容,导致无法启动项目,具体现象是:启动背景无报错,但是页面无内容
要把第一句的中间件获取换成这个,项目就可以正常启动了
- const {createProxyMiddleware: proxy} =require('http-proxy-middleware');
复制代码 例子
分别根据 /api1和 /api2 这两个前缀,去到差别的server上面
代码如下
可以看到都是访问自己的前端端口,假如当前资源没找到,就会去找上面配置的代理路径(假如配了多个url前缀,就会根据url前缀去差别server)
- class App extends React.Component {
- getData1 = () => {
- axios.get("http://localhost:3000/api1/search/users2").then(
- (response) => {
- console.log("成功了", response.data);
- },
- (error) => {
- console.log("失败了", error);
- }
- );
- };
- getData2 = () => {
- axios.get("http://localhost:3000/api2/search/users2").then(
- (response) => {
- console.log("成功了", response.data);
- },
- (error) => {
- console.log("失败了", error);
- }
- );
- };
- render() {
- return (
- <div className="todo-container">
- <button onClick={this.getData1}>获取数据1</button>
- <button onClick={this.getData2}>获取数据2</button>
- </div>
- );
- }
- }
- export default App;
复制代码 执行效果:
两个node的Server也都正常执行了
兄弟组件传值Pub-Sub
之前可以看到差别的组件之间传值只能通过state的状态提拔来举行传值,但是很显着这么做在大量数据交互的时候会变得巨难管理,所以我们要引入Pub-Sub.js来实现兄弟组件传值。
PubSub组件传值官网
npm安装命令
版本查看
快速起步
- import PubSub from 'pubsub-js' //引入
- PubSub.subscribe('用于识别的key', function(msg, data){ }); //订阅
- PubSub.publish('用于识别的key', data) //发布消息
复制代码 注意:
适用于恣意组件之前的消息沟通
谁用谁接,谁传谁发
特别注意:
肯定要先开启监听,再发布,要不然发半天没人听发了也没有用
发布消息(publish)
- 消息发布,第一个参数订阅key(后续订阅会用到),第二个参数是值,下面这两个都是完全可以的传值方式
- PubSub.publish('MY TOPIC', 'hello world!');
- PubSub.publish('MY TOPIC', { info: "info" });
- 还有一种同步发布,但是这种性能略强,但是不推荐,但有可能出现安全问题,慎重。用上面那两个就够了
- PubSub.publishSync('MY TOPIC', 'hello world!');
复制代码 好比这种,就可以传入对象进去,发布给obj-key的这个消息订阅方
注意这里传入对象不要用展开符
订阅消息(subscribe)
- // msg是必须接受的,如果实在不想接受可以传个占位符
- // var mySubscriber = function (_, data)
- var mySubscriber = function (msg, data) {
- console.log(msg, data);
- };
- // 生成一个token是为了后续停止订阅用的,有专门api来关闭订阅
- var token = PubSub.subscribe('MY TOPIC', mySubscriber);
- // 关闭订阅(可以用在组件卸载这种)
- PubSub.unsubscribe(token);
复制代码 也可以用箭头函数,这两种写法是完全等价的
- var mySubscriber = function (msg, data) {
- console.log(msg, data);
- };
- // 生成一个token是为了后续停止订阅用的,有专门api来关闭订阅
- var token = PubSub.subscribe('MY TOPIC', (msg, data)=>{
- // 打印出来的msg就是订阅key
- console.log(msg, data);
- });
复制代码 在实际代码中参加,发现token作用域出现问题了,所以要改一下
用this修改一下,再用箭头函数优化一下
代码示例
三个组件集成在一个文件里,实现测试
- import React from "react";
- import PubSub from "pubsub-js";
- import "./App.css";
- // 一定要先开启监听,再发布,要不然发半天没人听发了也没有用
- class PublishInfo extends React.Component {
- publishInfo = () => {
- var obj = { info: "info" };
- PubSub.publish("publish-key", obj);
- };
- render() {
- return (
- <div>
- <button onClick={this.publishInfo}>publishInfo(后开启发布)</button>
- </div>
- );
- }
- }
- // 一定要先开启监听,再发布,要不然发半天没人听发了也没有用
- class SubscriptInfo extends React.Component {
- subScriptInfo = () => {
- this.token = PubSub.subscribe("publish-key", (msg, data) => {
- // 这里只接受一个data也是可以的
- console.log(msg, data);
- });
- };
- unsubscribeBeforeUnmount = () => {
- // 模拟组件卸载前函数,暂停监听。当然放在生命周期里面也可以
- console.log("tokenID:", this.token, PubSub.unsubscribe(this.token));
- };
- render() {
- return (
- <div>
- <button onClick={this.subScriptInfo}>subScriptInfo(先开启监听)</button>
- <button onClick={this.unsubscribeBeforeUnmount}>
- 模拟卸载组件(最后)
- </button>
- </div>
- );
- }
- }
- // 创建并暴露App组件
- class App extends React.Component {
- render() {
- return (
- <div>
- <SubscriptInfo />
- <PublishInfo />
- </div>
- );
- }
- }
- export default App;
复制代码 非常场景
这种,从F组件给C组件传值,只要key是一样得,就可以通信到,所以跨组件传值非常方便
Fetch发送请求(扩展)
首先 fetch 也是一种发送请求的方式,它是在 xhr 之外的一种,我们平常用的 Jquery 和 axios 都是封装了 xhr 的第三方库,而 fetch 是js官方自带的库(也是XMLHttpRequest的升级版),同时它也采用的是 Promise 的方式,大大简化了写法
由于是js内置,所以甚至可以在控制台运行
在代码里如何使用呢?
- fetch("https://api.github.com/users/ruanyf")
- .then((response) => response.json())
- .then((json) => console.log(json))
- .catch((err) => console.log("Request Failed", err));
复制代码 搞个按钮触发一下fetch就可以用了
它的使用方法和 axios 非常的类似,都是返回 Promise 对象,但是差别的是, fetch 关注分离,它在第一次请求时,不会直接返回数据,会先返回联系服务器的状态,在第二步中才可以或许获取到数据
我们必要在第一次 then 中返回 response.json() 因为这个是包含数据的 promise 对象,response.json()是一个异步操作,取出所有内容,并将其转为 JSON 对象。再调用一次 then 方法即可获取到对象,
但是这么多次的调用 then 并不是我们所期望的,相信看过之宿世成器的文章的同伴,已经有了想法。
我们可以使用 async 和 await 配合使用,来简化代码
可以将 await 明白成一个主动执行的 then 方法,如许清楚多了
- async function getJSON() {
- let url = 'https://xxx';
- try {
- let response = await fetch(url);
- return await reasponse.json();
- } catch (error) {
- console.log('Request Failed', error);
- }
- }
复制代码 末了关于错误对象的获取可以采用 try...catch 来实现
关于 fetch 的更多内容
猛烈保举阮一峰老师的博文:fetch
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |