

model -> 接口层、模型层;store -> 全局状态;components -> UI 层
const Uploader = {
add(file, filename) {
const item = new AV.Object("Image");
const avFile = new AV.File(filename, file);
item.set("filename", filename);
item.set("owner", AV.User.current());
item.set("url", avFile);
return new Promise((resolve, reject) => {
item.save().then(
(serverFile) => resolve(serverFile),
(error) => reject(error)
);
});
},
};
💡:new AV.Object("Image")?
在构建对象时,为了使云端知道对象属于哪个 class,需要将 class 的名字作为参数传入。你可以将 LeanCloud 里面的 class 比作关系型数据库里面的表。一个 class 的名字必须以字母开头,且只能包含数字、字母和下划线。
所以创建了一张表:

💡:new AV.File(filename, file)?
有时候应用需要存储尺寸较大或结构较为复杂的数据,这类数据不适合用
AV.Object保存,此时文件对象AV.File便成为了更好的选择。文件对象最常见的用途是保存图片,不过也可以用来保存文档、视频、音乐等其他二进制数据。
示例:
const data = { base64: 'TGVhbkNsb3Vk' };
// resume.txt 是文件名
const file = new AV.File('resume.txt', data);
💡:item配合avFile使用?
AV.Object旗下的属性支持两种特殊的数据类型 Pointer 和 File,可以分别用来存储指向其他 AV.Object 的指针以及二进制数据
所以:
你上传了一张图片,就是在Image表里创建一条记录:

此刻,Image表里的这个item记录的这个url字段指向了File表里avFile这条记录

存储服务似乎用了七牛提供的存储服务!
💡:item.save()?
是个异步操作,把数据存储到远程数据库,如果存储成功,响应回来给我们的serverFile是这样一个东西:

import { observable, action, makeObservable } from "mobx";
import { Uploader } from "../models";
class ImageStore {
constructor() {
makeObservable(this);
}
@observable filename = "";
@observable file = null;
@observable serverFile = null;
@observable isUploading = false;
@action setFilename(newFilename) {
this.filename = newFilename;
}
@action setFile(newFile) {
this.file = newFile;
}
@action upload() {
this.isUploading = true;
return new Promise((resolve, reject) => {
Uploader.add(this.file, this.filename)
.then((serverFile) => {
this.serverFile = serverFile;
resolve(serverFile);
})
.catch((err) => {
console.error("上传失败");
reject(err);
})
.finally(() => (this.isUploading = false));
});
}
}
export default new ImageStore();
Uploader组件使用全局数据:

这跟之前的登录注册功能是一样的写代码姿势! ->
Store维护了我们要传给后台的参数
效果:

💡:为什么抽离出一个 Uploader 组件?
这个组件是给 Home 页面用的!而这个页面需要用到很多东西,如果不把上传功能抽离成一个组件,那么 Home 页面就很乱了,而这也体现不了使用 React 的优势了!
💡:非受控表单?
所谓受控和非受控,指的是我们对某个组件状态的掌控,如果它的值只能由用户设置,也就是使用 Web 应用的人,那么这个组件的状态是「非受控」的,如果开发者可以通过 JS 代码来控制组件的状态,那么这个组件就是「受控」的
在 HTML 的表单元素中,它们通常自己维护一套
state,并随着用户的输入自己进行UI上的更新,这种行为是不被我们程序所管控的。而如果将React里的state属性和表单元素的值建立依赖关系,再通过onChange事件与setState()结合更新state属性,就能达到控制用户输入过程中表单发生的操作。被React以这种方式控制取值的表单输入元素就叫做受控组件。
受控组件特点 -> 需要单独的为每个表单元素维护一个状态
而非受控组件 -> 利用ref属性,可以获取 input 元素的 DOM 属性信息,如获取用户输入的值 -> 用defaultValue属性来指定表单元素的默认值
对于 file 类型的表单控件它始终是一个不受控制的组件,因为它的值只能由用户设置,而不是以编程方式设置。
针对type类型的file的input元素,你要用非受控组件的方式去获取它的值! -> 这就是使用const ref = useRef()的原因!
总之你不能通过这样:
<input type="file" value={this.state.files} onChange={(e) => this.handleFile(e)} />
去改变这个元素的值!
file 表单元素的值是
fileRef.current.files->files(一个或多个文件,是个数组,没开启多选multiple的话,那么files的长度始终为1)
总之:
React 的官方说法是:绝大部分时候推荐使用受控组件来实现表单,因为在受控组件中,表单数据由React组件负责处理;当然如果选择受控组件的话,表单数据就由DOM本身处理了。
➹:受控和非受控组件真的那么难理解吗?(React 实际案例详解)
💡:onChange事件?
监听输入内容的改变
💡:IDL 属性?

我很好奇为啥是用files来获取元素的内容?而不是用value呢?
➹:Web 技巧(07). 在这一期中咱们一起来聊聊 HTML5 中的表单。说到 HTML

最终实现的效果:

代码:
import React from "react";
import { useStores } from "../stores";
import { observer } from "mobx-react";
import { Upload } from "antd";
import { InboxOutlined } from "@ant-design/icons";
const { Dragger } = Upload;
const Component = observer(() => {
const { ImageStore } = useStores();
const props = {
showUploadList: true,
beforeUpload: (file) => {
ImageStore.setFile(file);
ImageStore.setFilename(file.name);
// 异步操作
ImageStore.upload()
.then((serverFile) => console.log("上传成功", serverFile))
.catch((err) => console.log(err));
// 先结束
return false;
},
};
return (
<div>
<Dragger {...props}>
<p className="ant-upload-drag-icon">
<InboxOutlined />
</p>
<p className="ant-upload-text">
Click or drag file to this area to upload
</p>
<p className="ant-upload-hint">
Support for a single or bulk upload. Strictly prohibit from uploading
company data or other band files
</p>
</Dragger>
<div>
<h1>上传结果</h1>
{ImageStore.serverFile ? (
<div>{ImageStore.serverFile.attributes.url.attributes.url}</div>
) : null}
</div>
</div>
);
});
export default Component;
解析:
showUploadList:它的值为true,那就展示这个:

有种记录本地上传历史的调调……
beforeUpload:是个钩子,在上传文件到服务器之前触发,其实就是给上传文件到服务器这个操作用的!(resolve 时开始上传、返回false就停止文件上传) -> 我们从本地把文件拖进去,相当于给这个盒子输入了一个文件 -> 文件输入后,就可以着手把文件上传到服务器的事儿了!
💡:ImageStore.serverFile.attributes.url.attributes.url?

目前察觉到的问题:「不登录,也能上传图片」

不能让所有用户都可以无限上传,不然这免费的 API,就凉凉了……
用到的第三方组件:Message -> 文档:全局提示 Message - Ant Design
自定义的 Tips 组件:

代码:
import React from "react";
import { useStores } from "../stores";
import { observer } from "mobx-react";
import styled from "styled-components";
const Tips = styled.div`
padding: 10px;
margin: 30px 0;
color: #fff;
border-radius: 4px;
background-color: orange;
`;
const Component = observer(({ children }) => {
const { UserStore } = useStores();
return <>{UserStore.currentUser ? null : <Tips>{children}</Tips>}</>;
});
export default Component;
关键代码:

💡:消息弹出框默认会停留多久才会消失?
默认是 3 s!
要做的:

要展示哪些关于图片的信息呢?
实现的效果:

代码思路:判断ImageStore.serverFile是否存在,如果存在就把图片的信息展示到这个「上传结果」UI 里边就行了
完成后的效果:

💡:图片尺寸定制?
Leancloud 的文件商使用的是七牛的服务,根据七牛的文档,在 url 后面加上设定会获得相应的缩略图:
imageView2 提供简单快捷的图片格式转换、缩略、剪裁功能。只需要填写几个参数,即可对图片进行缩略操作,生成各种缩略图。
imageView2接口可支持处理的原图片格式有psd、jpeg、png、gif、webp、tiff、bmp。(webp 不支持动图)
API 规格:

➹:获得缩略图问题 url - 问题讨论 / SDK / API - LeanCloud 用户社区
➹:图片基本处理(imageView2)_API 文档_智能多媒体服务 - 七牛开发者中心
💡:useLocalStore?
视频里用的是useLocalStore,我用的是useLocalObservable!
局部的store:

组件依赖这个store:<Observer>{fn}<Observer>

mobx-react和mobx-react-lite可以混着用。..
为什么我不用useLocalStore,因为官方说它将要被废弃了 -> 官方推荐我们使用useLocalObservable代替!
总之:
useLocalStore+observer(() => JSX.Element) -> 'mobx-react'useLocalStore+useObserver(() => JSX.Element) -> 'mobx-react'<Observer>{() => JSX.Element}</Observer>useLocalObservable+<Observer>{() => JSX.Element}</Observer> -> mobx-react-lite➹:MobX 在 hook 中的使用 - SegmentFault 思否
💡:mobx-react-lite 介绍?
Mobx-react-lite —— Lightweight React bindings for MobX based on React 16.8 and Hooks
直至 2018 年底,React 项目中统一使用 mobx 的方式都是 mobx-react。然而随着 react hooks 的诞生,mobx-react-lite 也出现了,它是专门服务于 react hooks 的 mobx-react 轻量级版本。虽然 mobx-react@6 已经包含了 mobx-react-lite,但官方是推荐在没有类组件的 react 项目中直接使用 mobx-react-lite 的。
💡:rel="noreferrer"?
超链接
target="_blank"要增加rel="nofollow noopener noreferrer"来堵住钓鱼安全漏洞。如果你在链接上使用target="_blank"属性,并且不加上rel="noopener"属性,那么你就让用户暴露在一个非常简单的钓鱼攻击之下。
也就是说:
当你使用
target='_blank'打开一个新的标签页时,不加rel="..."的话,那么新页面的window对象上有一个属性opener,它指向的是前一个页面的window对象,因此,后一个页面就获得了前一个页面的控制权,而这是很可怕的!