搞钱项目总结

这个就是接了一个心理学院的项目,帮他们做一个测试卷嘛,一千五,哎,缺钱,接了,写的比较随意,但学了点东西,写篇总结。

React-Router v6

首先就是因为之前写的都是学长学姐已经把脚手架都搭好了,所以对于router的使用还停留在v5版本,然后这次自己新开一个脚手架,mad,router偷偷摸摸升级到v6,像什么Switch啊Redirect啥的直接移除了,所以写的时候有点崩溃,然后就看了文档跟一些博客后就回去接着写了,这里就简单的介绍一下v6的一些用法。

Routes

这没啥 就是换了个名字,代替Switch

这个 改的我 奇奇怪怪莫名其妙

以前很方便啊,用activeClassName,然后给自己自定义的类名写一点样式就可以做到自定义了,(以前是默认加上active的类名),然后升级到v6后,我们康康这个贼官方文档怎么说的

One difference as of v6.0.0-beta.3 is that activeClassName and activeStyle have been removed from NavLinkProps. Instead, you can pass a function to either style or className that will allow you to customize the inline styling or the class string based on the component’s active state.

我是真的想啸,直接给移除了,现在想改类名或者直接写行内样式的话得用函数形式

1
2
3
4
5
6
7
8
<NavLink
to="messages"
style={({ isActive }) =>
isActive ? activeStyle : undefined
}
>
Messages
</NavLink>

官网用例, className同理

同样换名 代替 Redirect

<Route path="*" element={<Navigate to'/login' />} />

Route element

就是v5 Route component + render 的结合体嘛, 就是可以给自己的子路由传props咯,也很简单

1
2
<Route path='/home' element= {<Home {...props} />} />

就是加了点to的方式,因为v6的嵌套路由相对于v5升级了很多(后面会提到),所以作为一种跳转方式Link to肯定也要相应的升级,这里直接看文档怎么说的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// If your routes look like this
<Route path="app">
<Route path="dashboard">
<Route path="stats" />
</Route>
</Route>

// and the current URL is /app/dashboard (with or without
// a trailing slash)
<Link to="stats"> => <a href="/app/dashboard/stats">
<Link to="../stats"> => <a href="/app/stats">
<Link to="../../stats"> => <a href="/stats">
<Link to="../../../stats"> => <a href="/stats">

// On the command line, if the current directory is /app/dashboard
cd stats # pwd is /app/dashboard/stats
cd ../stats # pwd is /app/stats
cd ../../stats # pwd is /stats
cd ../../../stats # pwd is /stats

.代表当前路径 ..代表上一级路径嘛

useRoutes

就是配置式路由, 代替react-router-config,不怎么常用吧好像,大部分还是愿意写JSX而不是JS来声明路由的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function App() {
let element = useRoutes([
// These are the same as the props you provide to <Route>
{ path: "/", element: <Home /> },
{ path: "dashboard", element: <Dashboard /> },
{
path: "invoices",
element: <Invoices />,
// Nested routes use a children property, which is also
// the same as <Route>
children: [
{ path: ":id", element: <Invoice /> },
{ path: "sent", element: <SentInvoices /> }
]
},
// Not found routes work as you'd expect
{ path: "*", element: <NotFound /> }
]);

// The returned element will render the entire element
// hierarchy with all the appropriate context it needs
return element;
}

代码大概长这样

useNavigate

代替useHistory, 然后看他的interface嘛navigate接受两个参数一个to 一个opt, to可以用来写路径或者直接1,-1这些数字,就是原来history里面的push啊go啊,然后opt就是用来设置是否replace和路由传参的(state参数)

嵌套路由

就是对于子路由的实现,v5版本得这样写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function App() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/profile" component={Profile} />
</Switch>
</BrowserRouter>
);
}

function Profile() {
let match = useRouteMatch();

return (
<div>
<nav>
<Link to={`${match.url}/me`}>My Profile</Link>
</nav>

<Switch>
<Route path={`${match.path}/me`}>
<MyProfile />
</Route>
<Route path={`${match.path}/:id`}>
<OthersProfile />
</Route>
</Switch>
</div>
);
}

菁程和工作台都是这样一个写法,然后到了v6,咱就可以

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// v6
import { BrowserRouter, Routes, Route, Link, Outlet } from "react-router-dom";

// Approach #1
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="profile/*" element={<Profile />} />
</Routes>
</BrowserRouter>
);
}

function Profile() {
return (
<div>
<nav>
<Link to="me">My Profile</Link>
</nav>

<Routes>
<Route path="me" element={<MyProfile />} />
<Route path=":id" element={<OthersProfile />} />
</Routes>
</div>
);
}

// Approach #2
// You can also define all
// <Route> in a single place
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="profile" element={<Profile />}>
<Route path=":id" element={<MyProfile />} />
<Route path="me" element={<OthersProfile />} />
</Route>
</Routes>
</BrowserRouter>
);
}

function Profile() {
return (
<div>
<nav>
<Link to="me">My Profile</Link>
</nav>

<Outlet />
</div>
);
}

注意,这里的 也是v6的新写法,就是表示子路由界面的Element

大概就是这些改动,具体还没有深读过,没办法,太菜了,还停留在调API的阶段。

关于Contnent-type

然后由于是开的JS项目,所以还是手写封装fetch,这个小幸运已经讲过了,原本我以为交互会很简单,结果,万万没想到,大家一定别找一个天天学C++的后端,就是一个需求是前端需要传一个数组,后端跟我说不行没法处理,不能绑定啥吧啦吧啦的,我寻思这不是欺负我不懂?但后端如果能接受Json的话我把数组转成Json再给他不行?再不济我转成字符串给他?然后找了一个我从来没见过的格式让我传,就是这个
postman

X-www-from-urlencoded,人给我看傻了,没见过这种数据格式,于是就上网搜了下,说实话,搜了也没看懂,因为实在没多少用,接下来就是直接搬的概念了。

请求格式

  1. form-data

就是http请求中的multipart/form-data,它会将表单的数据处理为一条消息,以标签为单元,用分隔符分开。既可以上传键值对,也可以上传文件。当上传的字段是文件时,会有Content-Type来说明文件类型;content-disposition,用来说明字段的一些信息;

由于有boundary隔离,所以multipart/form-data既可以上传文件,也可以上传键值对,它采用了键值对的方式,所以可以上传多个文件。

  1. X-www-form-urlencoded

就是application/x-www-from-urlencoded,会将表单内的数据转换为键值对,比如,name=java&age = 23

  1. raw 这个就是我们比较熟悉的的一种格式了,可以上传任意格式的文本,text/json/xml/html等,然后我们大部分还是传json过去,因为json可以序列化大部分数据类型嘛

  1. binary

       相当于Content-Type:application/octet-stream,从字面意思得知,只可以上传二进制数据,通常用来上传文件,由于没有键值,所以,一次只能上传一个文件。
    

multipart/form-data与x-www-form-urlencoded区别

           multipart/form-data:既可以上传文件等二进制数据,也可以上传表单键值对,只是最后会转化为一条信息;

           x-www-form-urlencoded:只能上传键值对,并且键值对都是间隔分开的。

然后我们就可以看到,这些格式在headers里面的content- type都同,那什么是content- type呢?

认识content-type

HTTP协议(RFC2616)采用了请求/响应模型。客户端向服务器发送一个请求,请求头包含请求的方法、URI、协议版本、以及包含请求修饰符、客户信息和内容的类似于MIME的消息结构。服务器以一个状态行作为响应,相应的内容包括消息协议的版本,成功或者错误编码加上包含服务器信息、实体元信息以 及可能的实体内容。

通常HTTP消息由一个起始行,一个或者多个头域,一个只是头域结束的空行和可选的消息体组成。
HTTP的头域包括通用头,请求头,响应头和实体头四个部分。每个头域由一个域名,冒号(:)和域值三部分组成。域名是大小写无关的,域值前可以添加任何数量的空格符,头域可以被扩展为多行,在每行开始处,使用至少一个空格或制表符。

请求消息和响应消息都可以包含实体信息,实体信息一般由实体头域和实体组成。实体头域包含关于实体的原信息,实体头包括Allow、Content- Base、Content-Encoding、Content-Language、 Content-Length、Content-Location、Content-MD5、Content-Range、Content-Type、 Etag、Expires、Last-Modified、extension-header。

Content-Type是返回消息中非常重要的内容,表示后面的文档属于什么MIME类型。Content-Type: [type]/[subtype]; parameter。例如最常见的就是text/html,它的意思是说返回的内容是文本类型,这个文本又是HTML格式的。原则上浏览器会根据Content-Type来决定如何显示返回的消息体内容。

content-type 和Accept

然后在之前有关fetch封装的代码里面,我们可以看到基本上只设置了两个值,Accept和content- type,那我们看看这二者的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(1)Accept属于请求头, Content-Type属于实体头。
Http报头分为通用报头,请求报头,响应报头和实体报头。
请求方的http报头结构:通用报头|请求报头|实体报头
响应方的http报头结构:通用报头|响应报头|实体报头

(2)Accept代表发送端(客户端)希望接受的数据类型。
比如:Accept:text/xml;
代表客户端希望接受的数据类型是xml类型

Content-Type代表发送端(客户端|服务器)发送的实体数据的数据类型。
比如:Content-Type:text/html;
代表发送端发送的数据格式是html。

二者合起来,
Accept:text/xml;
Content-Type:text/html
即代表希望接受的数据类型是xml格式,本次请求发送的数据的数据格式是html

3.content-type 概览

这就贴一些常见的格式

常见的媒体格式类型如下:

1
2
3
4
5
6
text/html : HTML格式
text/plain :纯文本格式
text/xml : XML格式
image/gif :gif图片格式
image/jpeg :jpg图片格式
image/png:png图片格式

以application开头的媒体格式类型:

1
2
3
4
5
6
7
8
9
application/xhtml+xml :XHTML格式
application/xml : XML数据格式
application/atom+xml :Atom XML聚合格式
application/json : JSON数据格式
application/pdf :pdf格式
application/msword : Word文档格式
application/octet-stream : 二进制流数据(如常见的文件下载)
application/x-www-form-urlencoded : <form encType=””>中默认的encType,form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式)

以audio开头的常见媒体格式文件:

1
2
3
'audio/x-wav' : wav文件
'audio/x-ms-wma' : wma 文件
'audio/mp3' : mp3文件

以video开头的常见媒体格式文件:

1
2
3
'video/x-ms-wmv' : wmv文件
'video/mpeg4' : mp4文件
'video/avi' : avi文件

另外一种常见的媒体格式是上传文件之时使用的:

1
multipart/form-data : 需要在表单中进行文件上传时,就需要使用该格式

注意,fetch的默认方式是text,而AJAX是urlencoded

但是,实际开发中,基本上还都是用raw的格式,因为Json太好用了,后端拿到数据后也不需要进行过多的处理,看了菁程和工作台,基本上都是只有application/json这一种格式,哪怕需要有文件上传,比如头像的上传,都会先转换成文本来处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 上传图片后的处理方式  
changeImg(changeKind) {
const imgFile = this[changeKind].files[0];

if (imgFile) {
if (!/image\/\w+/.test(imgFile.type)) return false;

const reader = new FileReader();
const _this = this;
// 将文件以Data URL形式进行读入页面
reader.readAsDataURL(imgFile);

reader.onload = e => {
_this.setState({
img: e.target.result,
imgFile
});
};
}

return this;
}
// 保存头像后的处理
if (imgFile) {
const data = new FormData();
data.append("image", imgFile);

ManageService.savePersonalAvatar(data)
.then(() => {
this.setState({ ifSave: true });
Store.dispatch({
type: "substituteAvatar",
payload: img || ""
});
}

然后又在fetch中对body进行Json序列化,总的来说虽然动用FormData对象,但上传的时候依旧是Json数据。那不用Json怎么传?我只能说给大家看看原本的urlcoded长啥样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 注释的部分是原本的urlcoded格式写法,主要要更改headers里面的content-type   
message(result, group_id) {
// let postResult = JSON.stringify(result)
// let urlencoded = new URLSearchParams(BASE)
// for (let i = 0; i < result.length; i++) {
// urlencoded.append("result", result[i])
// }
// urlencoded.append("group_id", `${group_id}`)
// let urlStr = urlencoded.toString() 这里的urlStr类似于query参数的形式,&result=1这种
return Fetch(BASE + '/message', {
method: 'POST',
data: {
result: result,
group_id: `${group_id}`
}
})
}
};

大概就是这些,算是对自己http知识方面的一个补充,这个项目逻辑也不算难,用状态提升就能解决大部分问题,主要自己写的比较赶,代码有点丑,所以多的也不放了,挣钱嘛,不寒颤。