Skip to content

Commit

Permalink
fix: 修复isPage错误
Browse files Browse the repository at this point in the history
chore: 开发文档

test: 测试用例
  • Loading branch information
missannil committed Jan 1, 2024
1 parent 9f62cd4 commit 301366d
Show file tree
Hide file tree
Showing 41 changed files with 579 additions and 26 deletions.
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ annil(安奈儿)是微信小程序原生开发插件,相较于原生API,使用an

- **组件构建API功能更强大**

新的组件构建API加入了[computed](./doc/fields/computed.md)[watch](./doc/fields/watch.md)[store](./doc/fields/store.md)(基于mobx的全局响应式数据)`等功能,使开发更便捷。
新的组件构建API加入了[computed](./doc/demo/computed.md)[watch](./doc/demo/watch.md)[store](./doc/demo/store.md)(基于mobx的全局响应式数据)`等功能,使开发更便捷。

- **复杂组件解决方案**

Expand All @@ -42,10 +42,6 @@ annil(安奈儿)是微信小程序原生开发插件,相较于原生API,使用an

新的组件构建API(DefineComponent)返回的类型叫组件(文档)类型,好比传统组件(UI)库为每个组件书写的使用文档,在做为子组件构建新组件(页面)时,子组件API(SubComponent)要求使用者输入组件类型,在定义选项字段时,会得到类型提示、约束和检测。这样实现了一个页面中所有子组件之间的类型耦合,无论组件嵌套多少层,无论哪层组件数据类型发生改变,所有相关组件类型都会得到感知。当您增改、重构代码时,只要无类型报错(tsc --noEmit --watch)就不会有运行时报错的心智负担。

- **类型约束书写规范**

annil通过类型来约束代码书写规范,例如在js开发时可以通过`this.setData({...})`新增一个未在data字段中定义的数据或修改properties中定义的数据,这显然是不对的。新API通过类型报错的形式约束[书写规范](./doc/designIdea.md),在特定情形下(例如测试)你可以通过 `//@ts-ignore``as any`关闭类型检测。

- **高兼容性**

annil提供的API都是原生API的语法糖,不具有强制性和侵入性,可渐进性使用或重构代码。
Expand All @@ -58,6 +54,10 @@ annil(安奈儿)是微信小程序原生开发插件,相较于原生API,使用an

第三方组件库一般都是以文档的形式对组件说明,插件提供了泛型[GenerateDoc](./src/types/GenerateDoc.ts),可根据组件文档快速书写组件类型,使第三方组件融入组件构建模型,后续插件会陆续添加原生(Wm)和常用第三方组件库(Vant)类型。

- **设计思想**

请参考[设计思想](./doc/designIdea.md)

### 安装

1. 依赖安装
Expand Down Expand Up @@ -130,6 +130,8 @@ annil(安奈儿)是微信小程序原生开发插件,相较于原生API,使用an

[navigateTo](./doc/api/navigateTo.md)

- 实用类型

### 更新日志

[CHANGELOG](./CHANGELOG.md)
54 changes: 54 additions & 0 deletions doc/demo/computed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
**RootComponent下的computed字段**

```ts
import { DefineComponent, type DetailedType, RootComponent } from "annil";
import { observable } from "mobx";

type User = { name: string; age: number };

const storeUser = observable<User>({
name: "zhao",
age: 20,
});

const rootComponent = RootComponent()({
properties: {
user: Object as DetailedType<User>,
userOptional: {
type: Object as DetailedType<User>,
value: { name: "zhao", age: 20 },
},
},
data: {
Duser: { name: "zhao", age: 20 },
},
store: {
Sage: () => storeUser.age,
},

computed: {
// 1. 引用properties字段
age() {
return this.data.user?.age || 0;
},
// 2. 引用data字段
Dname() {
return this.data.Duser.name;
},
// 3. 引用store字段
SagePlus() {
return this.data.Sage + 1;
},
// 5. 引用其他计算属性
refOtherComputedFields() {
return this.data.age + this.data.SagePlus;
},
},
});
const computed = DefineComponent({
name: "computed",
rootComponent,
});

export type $Computed = typeof computed;
```
51 changes: 51 additions & 0 deletions doc/demo/store.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
### store字段 可引入基于mobx的全局状态(响应式数据)。

```ts
import { DefineComponent, type DetailedType, RootComponent } from "annil";
import { observable } from "mobx";

type User = { name: string; age: number };

const storeUser = observable<User>({
name: "zhao",
age: 20,
changeName(name: string) {
this.name = name;
},
changeAge(age: number) {
this.age = age;
},
});
type DemoComp = { properties: { demo_age: number } };
const rootComponent = SubComopnent<{}>()({
store: {
demo_age: () => storeUser.age,
},
lifetimes: {
attached() {
console.log(this.data.demo_age); // 20
storeUser.changeAge(30);
console.log(this.data.demo_age); // 30
},
},
});
const rootComponent = RootComponent()({
store: {
name: () => storeUser.name,
},
lifetimes: {
attached() {
console.log(this.data.name); // 'zhao'
storeUser.changeName("li");
console.log(this.data.name); // 'li'
},
},
});

const store = DefineComponent({
name: "store",
rootComponent,
});

export type $Store = typeof store;
```
66 changes: 66 additions & 0 deletions doc/demo/watch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
### watch字段

可定义所有数据字段(properties,data,store,computed中定义的),数据改变时(深度相等比较)运行定义函数,参数包含旧值,可监控多个字段或子字段。

```ts
import { DefineComponent, type DetailedType, RootComponent } from "annil";
import { observable } from "mobx";

type User = { name: string; age: number };

const storeUser = observable<User>({
name: "zhao",
age: 20,
changeName(name: string) {
this.name = name;
},
});

const rootComponent = RootComponent()({
properties: {
user: Object as DetailedType<User>, // 默认null
},
data: {
Duser: { name: "zhao", age: 20 },
},
store: {
name: () => storeUser.name,
},
computed: {
age() {
return this.data.user?.age || 0;
},
},
watch: {
user(newValue, oldValue) {
console.log(newValue, oldValue); // {name:'li',age:30}, null
},
"Duser.age"(newValue, oldValue) {
console.log(newValue, oldValue); // 30, 20
},
name(newValue, oldValue) {
console.log(newValue, oldValue); // "li", "zhao"
},
age(newValue, oldValue) {
console.log(newValue, oldValue); // 30, 0
},
},
lifetimes: {
attached() {
this.setData({
"Duser.age": 30,
// 模拟properteis.user传入新值
user: { name: "li", age: 30 },
});
storeUser.changeName("li");
},
},
});

const watch = DefineComponent({
name: "watch",
rootComponent,
});

export type $Watch = typeof computed;
```
3 changes: 3 additions & 0 deletions jest/InternalFieldProtection/InternalFieldProtection.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"component": true
}
17 changes: 17 additions & 0 deletions jest/InternalFieldProtection/InternalFieldProtection.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import simulate from "miniprogram-simulate";
import path from "path";

describe("内部字段保护", () => {
test("methods中包含了disposer字段--报错", () => {
try {
const id = simulate.load(path.resolve(__dirname, "InternalFieldProtection"));
const comp = simulate.render(id);
const parent = document.createElement("parent-wrapper");

comp.attach(parent);
} catch (error) {
// @ts-ignore
expect(error.message).toBe("disposer已被内部字段占用");
}
});
});
14 changes: 14 additions & 0 deletions jest/InternalFieldProtection/InternalFieldProtection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { DefineComponent, RootComponent } from "../../src";

const rootComponent = RootComponent()({
methods: {
disposer() {
// ...
},
},
});
const compA = DefineComponent({
name: "/compA",
rootComponent,
});
export type $CompA = typeof compA;
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"component": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import simulate from "miniprogram-simulate";
import path from "path";
export const tempObj = { result: false };

describe("页面virtualHost字段会被删除", () => {
const id = simulate.load(path.resolve(__dirname, "deleteVirtualHostWhenIsPage"));
const comp = simulate.render(id);
const parent = document.createElement("parent-wrapper");

comp.attach(parent);

test("页面virtualHost字段会被删除", () => {
expect(tempObj.result).toBeTruthy();
});
});
25 changes: 25 additions & 0 deletions jest/deleteVirtualHostWhenIsPage/deleteVirtualHostWhenIsPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { DefineComponent, RootComponent } from "../../src";
import { tempObj } from "./deleteVirtualHostWhenIsPage.test";

const rootComponent = RootComponent()({
isPage: true,

options: {
virtualHost: true, // 模拟注入的virtualHost字段
},
lifetimes: {
beforeCreate(options) {
tempObj.result = options.options!.virtualHost ?? true;
},
created() {
// 模拟页面给 is和route假值。因为isPageCheck是根据is和route来的。
this.is = this.route = "pages/index/index";
},
},
});
const index = DefineComponent({
path: "/pages/index/index",
rootComponent,
// subComponents:[]
});
export type $Index = typeof index;
Empty file.
4 changes: 4 additions & 0 deletions jest/isPageCheck/IsComponent/IsComponent.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}
16 changes: 16 additions & 0 deletions jest/isPageCheck/IsComponent/IsComponent.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import simulate from "miniprogram-simulate";
import path from "path";

describe("构建组件时isPage:false是正确的", () => {
const id = simulate.load(path.resolve(__dirname, "IsComponent"));
const comp = simulate.render(id);
const parent = document.createElement("parent-wrapper");
const onTap = jest.spyOn(console, "error");

test("不报错", () => {
comp.attach(parent);

// 没有报错行为
expect(onTap).toHaveBeenCalledTimes(0);
});
});
20 changes: 20 additions & 0 deletions jest/isPageCheck/IsComponent/IsComponent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { DefineComponent, RootComponent } from "../../../src";

const rootComponent = RootComponent()({
isPage: false,
data: {
result: true,
},
lifetimes: {
created() {
// 模拟组件给 is 一个路径。因为isPageCheck是根据is和route来的。
this.is = "components/compA/compA";
},
},
});
const compA = DefineComponent({
name: "compA",
rootComponent,
// subComponents:[]
});
export type $CompA = typeof compA;
3 changes: 3 additions & 0 deletions jest/isPageCheck/IsComponent/IsComponent.wxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<view class="events" data-id="events" bind:tap="onTap">{{id}}</view>


4 changes: 4 additions & 0 deletions jest/isPageCheck/IsComponentError/IsComponentError.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}
16 changes: 16 additions & 0 deletions jest/isPageCheck/IsComponentError/IsComponentError.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import simulate from "miniprogram-simulate";
import path from "path";

describe("构建组件时isPage:true是错误的", () => {
const id = simulate.load(path.resolve(__dirname, "IsComponentError"));
const comp = simulate.render(id);
const parent = document.createElement("parent-wrapper");
const onTap = jest.spyOn(console, "error");

test("报错", () => {
comp.attach(parent);

// 报错1次 `组件 components/compA/compA 中 RootComponent构建组件时,可不写isPage字段或值为 false`
expect(onTap).toHaveBeenCalledTimes(1);
});
});
20 changes: 20 additions & 0 deletions jest/isPageCheck/IsComponentError/IsComponentError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { DefineComponent, RootComponent } from "../../../src";

const rootComponent = RootComponent()({
isPage: true, // 故意写错 1
data: {
result: true,
},
lifetimes: {
created() {
// 模拟组件给 is 一个路径。因为isPageCheck是根据is和route来的。
this.is = "components/compA/compA";
},
},
});
const compA = DefineComponent({
path: "/compA", // 故意写错 2
rootComponent,
// subComponents:[]
});
export type $CompA = typeof compA;
3 changes: 3 additions & 0 deletions jest/isPageCheck/IsComponentError/IsComponentError.wxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<view class="events" data-id="events" bind:tap="onTap">{{id}}</view>


4 changes: 4 additions & 0 deletions jest/isPageCheck/IsPage/IsPage.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

0 comments on commit 301366d

Please sign in to comment.