GithubHelp home page GithubHelp logo

record's People

Contributors

lubyxu avatar

Stargazers

 avatar

Watchers

 avatar  avatar

record's Issues

微信小程序与企业微信小程序兼容方案

最近在做一个项目是,将微信小程序迁移到企业微信小程序中。但是遇到了一些问题:

  • 1、有些API,微信与企业微信不兼容
  • 2、新需求:tobe的项目,需要根据不同的目标,调用不同域名的接口。

问题一:有些API,微信与企业微信不兼容

解决的方案有很多种,其中最简单粗暴的是,在每个调用wx.api的地方做一个判断:

// wx.login({})  => isQyWx ? wx.qy.login : wx.login

显然这个是改动量最大的。

感谢ES6 proxy。我们将所有调用wx的地方,采用调用wx代理的方式解决问题:

// 小程序启动文件
const isWxEnv = wx.getSystemInfoSync()?.environment === 'wxwork';

(function () {
  wx = new Proxy(wx, {
    get(target, propKey, recevier) {
      if (isWxEnv && propKey === 'login') {
        return wx.qy.login;
      }
      return target[propKey];
    }
  })
})();

问题二:接口的域名需要动态获取,不能写死。

同样,在wx.request上做代理。做以下来个步骤:

const wxRequestProxy = async (fn, {url, method, data, ...params}: any) => {
    let baseUrl = ''
   // step1: 远程获取baseUrl,并设置缓存
    if (!url.startsWith('http')) {
        baseUrl = await getBaseUrl();
    }
    // 调用wx.request。 这里不能直接调用。
    // 如果只写调用wx.request,这是的wx则为代理,会出现死循环
    return fn.call(this, {
        ...params,
        method,
        data: method === 'GET' ? null : data,
        url: method === 'GET' ? qs.stringifyUrl({url: baseUrl + url, query: data}) : (baseUrl + url)
    });
};


(function () {
  wx = new Proxy(wx, {
    get(target, propKey, recevier) {
      if (isWxEnv && propKey === 'login') {
        return wx.qy.login;
      }
      if (propKey === 'request') {
        return (params) => wxRequestProxy.call(this, target[propKey], params);
      }
      return target[propKey];
    }
  })
})();

总结

平时写业务代码写太多了,很少会用到Proxy,Reflect。总算碰到了一个。记录下来。

关于对跨域问题的理解

原先对跨域问题的理解,只是停留在面试的基础上。前几天开发的一个关于认证的一个dev-server插件,出现了微服务之间的跨域问题。当对devServer加上

headers: {
    'Access-Control-Allow-Origin': '*',
},

时,就开启的跨域支持。

那么问题来了:为什么加上Access-Control-Allow-Origin头,浏览器就支持了呢?
通过charles抓包,就能发现,当不加Access-Control-Allow-Origin头,其实后端是正常返回的:
image

只是到了浏览器这一层面,会检查请求是否和origin同源,如果不同源的话,浏览器层面拦截。
image

那么如何让浏览器知道,我这个请求是安全的呢?这就需要后端在response的header里面加上Access-Control-Allow-Origin头,告诉浏览器,我这个请求麻烦请通过一下。

这样,浏览器拿到这个header之后,自然就会放过这个请求。

而webpack的devServer中的headers就是在response中加上headers。
image

一个带有条件判断的 graphql client schema


export const INIT_FORM = gql`
    query initFormFieldsAndValue($request1: ListFieldsByObjIdReq, $request2: ReqIdWithForm, $requestInitValue: Boolean!) {
        getFormValueById (request: $request2) @include(if: $requestInitValue) {
            id
            obj_type
            obj_id
            form_id
            vars
        }

        listFieldsByObjId(request: $request1) {
            items {
                field_id
                setting {
                    id
                    name
                    key
                    field_type
                    filter_order
                    enum_value {
                        value: key
                        text: value
                    }
                    enum_map
                    display_type
                    enum_items{
                        key
                        value
                        desc
                        en_value
                        en_desc
                        meta{
                            color
                        }
                    }
                    config
                }
            }
        }
    }
`;

局限性: @include 后面的参数只能是boolean

如何处理timeout,无权限等错误页面

如何抽象出这种类似的错误页面????

image

背景:当接口报一些没权限的错误时,如何处理这个问题?

方案一:在api层处理

有一个错误页面统一处理错误:ErrorPage
api返回成功后,判断errno,如果是NO_AUTH,跳转到错误页面,加上query。type=no-auth。

缺点:这个方案,看起来没有问题,但是也有很多问题。下面列出几个:
对于小程序来说

  • 错误页面,有第二个步骤:
    • 如”刷新重试“。那这个时候,ErrorPage需要记录上一页的路径,才能实现。因此,需要加页面加一个协议:backUrl=xxxxxxxx
    • 如果”重新扫码“。这个时候ErrorPage需要加上一个协议backUrl=scanCode,然后加上扫码逻辑。这个时候,问题又来了,扫码跳转到哪呢。

因此,当错误多的时候,显然ErrorPage上的路由协议是难以维护的。同时ErrorPage里面的业务逻辑也是难以维护的。

总结:这个方案,可以临时使用,快速上线,但是显然,维护成本比较高。

方案二:利用Suspense,接口报错的时候,往上抛异常(Promise.reject)。

这个方案分两个步骤:一个是接口出错时候的throw Promise.reject(注意,这个是Promise,不能是throw Error, 原因是Suspense,本身fallback出发,需要底层throw Promise.reject)。二个是为Suspense添加不同的fallback UI。

这个能解决的问题:我们将fallback的处理,分到了各个页面上,将

@nestjs/graphql 中,如何设置http头部。关于apollo-server-express

背景

业务代码开发过程中,经常需要设置set-cookie,302,等这些header信息。但是集成在@nestjs/graphql中,我们抛出的error,形成在了graphql的response body中。没办法报http error code。 基于这些原因,本人稍微往里看了看,总计如下:

Apollo-server 嵌入

image

nestjs背后,默认运行的是一个express服务。业务实现中,会首先创建一个app: NestFactory.create(AppModel),那么在AppModel中,我们会把ApolloServer植入进nestjs中。
实际上,在代码中,@nestjs/graphql将apollo-server-express已第三方中间件的方式,注入到了nestjs中。

apollo-server-express

fb34fba1770b87debffa25cc084712fa.png
image

apollo-server-express中的graphqlExpress将核心的工作交给了apollo-server-core中的runHttpQuery处理,并收尾runHttpQuery一些工作:

将runHttpQuery返回的responseInit设置response headers。
将runHttpQuery返回graphqlResponse直接res.send出去。(阶段了后续的中间件工作)。
具体代码,请看代码

runHttpQuery

runHttpQuery的主要工作是处理request中的请求,执行graphql代码,并返回response给中间层。

请求上下文requestContext

apollo-server-express会给每个请求生成一个上下文(requestContext),用来共享数据。

{
logger: options.logger || console,
schema: options.schema,
schemaHash: options.schemaHash,
request,
response: {
http: {
headers: new Headers(),
},
},
context,
cache: options.cache,
debug: options.debug,
metrics: {},
}
然后进入这个request生命周期,处理生命周期以及执行请求。apollo-server-express起了一个有趣的名字:requestPipline

image

requestPipline

image

requestPipline会走过request自己的生命周期。最后将response挂在requestContext上。

返回的response是Graphql的response结构:

export interface GraphQLResponse {
  data?: Record<string, any> | null;
  errors?: ReadonlyArray<GraphQLFormattedError>;
  extensions?: Record<string, any>;
  http?: Pick<Response, 'headers'> & Partial<Pick<Mutable<Response>, 'status'>>;
}

其中的http,代表的response的返回头部,最终会被赋值给express的res。代码

requestPipline生命周期调用

在代码中看见最多的是dispatcher.invokeHookAsync调用生命周期hook。dispatcher是一个订阅发布实现的调度器。代码。在初始化的时候,会拿到apollo-server中的plugins,这些plugins其实是一堆listeners。然后在执行流程中触发即可。

解决方案

定义一个plugin,处理http头部:

{
  requestDidStart(requestContext) {
    return {
      willSendResponse(requestContext) {
        const { data } = requestContext.response;
        const host = requestContext.request.http.headers.get('host');
        if (data?.login?.token) {
          requestContext.response.http.headers.set(
             'Set-Cookie', '_t=' + data?.login?.token
                + '; Domain=' + domain + '; Path=/;'
           );
       }

        if (data?.operation) {
            requestContext.response.http.status = 302;
       }
    };
  }
}

一个特别基础的js问题

今天碰到了一个问题,__DEV__ is not defined。 想了半天,不知道为什么!!!

__DEV__ 没有的话,不是会一直往上找的么!!直到找到这个为止,找不到默认就是undefined,不应该报错呀!!

但是!!!!我错了。。。
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Errors/Not_defined

对于getter来说,如果整条作用域链上没有这个变量,会报 is not defined错误。
但是!对于setter而言。如果整条作用域链 没有这个变量,会做两件事情:

// 1、先在globalThis上声明这个变量
var __DEV__;
// 2、 再对这个变量赋值。
__DEV__ = 1;

!!!!!!!!多么基础的问题啊。。。。。。我居然没想到。。。。。可见生活之中处处是基础。

关于draft-js

背景

最近公司有一个类似富文本的一个需求。类似markdown。为了满足公司个性化的一些问题,最终在方案选型上,选择了比较偏底层的draft-js

关于draft-js有一些概念,根据一些真实体验,重新定位了一下:

image

概念

EditorState

EditorStateImmutable类型),表示整个编辑器对象。包含三个模块:ContentStateSelectionStateCompositeDecorator。还有一些公共的方法:撤销/重置。

ContentState

ContentStateImmutable类型),表示编辑器里的所有内容。内容由单元(ContentBlock)组成。段落里面会有一些Entity(实例对象),比如对于image来说,他的ContentBlock是一个atomic,需要对这个block创建一个entity用来存储image背后的url

一个简单的例子:
image

contentState返回如下:

image

由此可以看出,这里的ContentState3组ContentBlock,每个block代表一个段落。他的entityMap为空。

CharacterMetadata

CharacterMetadata代表每一个字符的元数据。比如对于文本hello来说

// 假设contentBlock的text是hello
// contentBlock.getCharacterList() 返回的则是
// [
//     {style: [], entity: null},
//     {style: [], entity: null},
//     {style: [], entity: null},
//     {style: [], entity: null},
//     {style: [], entity: null},
// ]
// 数组中 第一个元素表示 'h'这个字符,他的style是个[],并且没有entity

SelectionState

SelectionStateImmutable类型),表示编辑器里的选择区域对象。既然是选择区域,那就有开始和结束。

  • anchor:表示光标开始的地方。
  • focue:表示光标最后聚焦的地方。

比如:
image

他的SelectionState

image

anchorKey: 代表区域start的block.keyanchorOffset:从block.key到开始的偏移量。

focusKey:代表区域end的block.keyfocusOffset:从block.key到开始的偏移量。

从结论来看用户选择区域的行为是从后往前。从前往后的返回如下
image

CompositeDecorator

由于inlineblock的样式不能覆盖全部富文本样式。比如可能要需要高亮、需要加背景。这个时候就想要用的自定义的组件了。

CompositeDecorator可以扫描所有ContentBlock,如果满足某些策略的话,就可以引用自定义的组件。

storybook with monorepo . `react-scripts`解析失败

@storybook/preset-create-react-app中,getScriptsPath会在require.resolve('react-scripts')失败。导致react-scripts相关的webpack config应用不到项目中。

原因,来自于@storybook/preset-create-react-app的bug。react-scripts中的package.json没有定义相关的入口文件,导致require.resolve('react-scripts')失败。以下是相关的临时解决方案:

const rewiredPath = require.resolve('@storybook/preset-create-react-app/dist/helpers/getReactScriptsPath');
const rewired = require(rewiredPath);

require.cache[rewiredPath].exports = {
    getReactScriptsPathWithYarnPnp: rewired.getReactScriptsPathWithYarnPnp,
    getReactScriptsPath: () => {
        const path = require.resolve('react-scripts/package.json');
        return path;
    },
};


const presets = require('@storybook/preset-create-react-app');

exports = {
    ...presets
};

关于vscode jsconfig.json无法只能提示的问题

编写了jsconfig的compilerOptions.paths后,下图的效果仍然无效。
image

经stackoverflow显示,缺少module的设置。设置module后成功。 具体jsconfig.json如下:

{
  "compilerOptions": {
    "target": "es2015",
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    },
    "experimentalDecorators": true,
    "module": "esnext"
  },
  "exclude": ["node_modules", "dist"]
}

记录antd design popover的一个采坑

背景:需要做一个新手引导,在左上角、右下角要有个气泡。气泡里面有一个标签、外部获取图片资源。
问题:左上角的popover不会抖动,右下角气泡经常抖动。

原因:右下角抖动原因是,在popover右下角弹出后,img外部没有设置宽、高。导致div默认height是0,当资源下载完毕,回流。整个popover的高度变化,才会到正确的位置。

解决方案:设置img的宽、高

React-Konva

背景

React-Konva 主要作用是将Konva的创建出来的节点,通过react-reconciler来做协调。 React-KonvaKonva节点,当做虚拟节点。因此,能看到React-Konva 代码里,有这么一段很奇怪的代码:

export const Layer = 'Layer';
export const FastLayer = 'FastLayer';
export const Group = 'Group';
export const Label = 'Label';
export const Rect = 'Rect';
export const Circle = 'Circle';
export const Ellipse = 'Ellipse';
export const Wedge = 'Wedge';
export const Line = 'Line';
export const Sprite = 'Sprite';
export const Image = 'Image';
export const Text = 'Text';
export const TextPath = 'TextPath';
export const Star = 'Star';
export const Ring = 'Ring';
export const Arc = 'Arc';
export const Tag = 'Tag';
export const Path = 'Path';
export const RegularPolygon = 'RegularPolygon';
export const Arrow = 'Arrow';
export const Shape = 'Shape';
export const Transformer = 'Transformer';

单纯的字符串的导出。那他到底划出来的呢!!!

感恩:

import ReactFiberReconciler from 'react-reconciler';

const KonvaRenderer = ReactFiberReconciler(HostConfig);

KonvaRenderer有两个关键的APIKonvaRenderer.createContainer(stage.current)返回一个根的Fiber节点,KonvaRenderer.updateContainer(props.children, fiberRef.current); 用于更新操作。

用法

import React from 'react';
import { Text } from 'react-konva';

const Demo = () => {
  return <Text text="hello" />
};

上述代码被转化为:

import React from 'react';
import { Text } from 'react-konva';

const Demo = () => {
  return React.createElement('Text', {
    text: 'hello'
  }, null);
};

react-reconciler

HostConfig

createInstance(type: Type, props, rootContainer, hostContext)

翻译

创建一个节点,相当于document.createElement(type)。在创建之后,返回之前,我们会常常设置这个节点的属性(除了一些事件句柄)。这是因为,当一个节点创建了之后,他并不能保证被挂在到这棵树上。

 /**
  * This method should return a newly created node. For example, the DOM renderer would call `document.createElement(type)` here and then set the properties from `props`.
  *
  * You can use `rootContainer` to access the root container associated with that tree. For example, in the DOM renderer, this is useful to get the correct `document` reference that the root belongs to.
  *
  * The `hostContext` parameter lets you keep track of some information about your current place in the tree. To learn more about it, see `getChildHostContext` below.
  *
  * The `internalHandle` data structure is meant to be opaque. If you bend the rules and rely on its internal fields, be aware that it may change significantly between versions. You're taking on additional maintenance risk by reading from it, and giving up all guarantees if you write something to it.
  *
  * This method happens **in the render phase**. It can (and usually should) mutate the node it has just created before returning it, but it must not modify any other nodes. It must not register any event handlers on the parent tree. This is because an instance being created doesn't guarantee it would be placed in the tree — it could be left unused and later collected by GC. If you need to do something when an instance is definitely in the tree, look at `commitMount` instead.
*/

对于react-knova来说

// createInstance 只列出了核心代码
export function createInstance(type, props, internalInstanceHandle) {
  let NodeClass = Konva[type]; // type 是 Layer、Group 等 会找到
  const instance = new NodeClass(propsWithoutEvents);
  applyNodeProps(instance, propsWithOnlyEvents); // 将event设置到节点上,并绘制在canvas上。
  return instance;
}

createTextInstance(text: string, rootContainer, hostContext)

翻译

text节点的createInstance

/**
  * Same as `createInstance`, but for text nodes. If your renderer doesn't support text nodes, you can throw here.
*/

对于react-knova来说

不做处理。

appendInitialChild(parentInstance: Instance, child: Instance | TextInstance)

翻译

会改变parentInstance,会增加child到他的子节点中。类似于DOM中的parentInstance.appendChild(child)

/**
 * This method should mutate the `parentInstance` and add the child to its list of children. For example, in the DOM this would translate to a `parentInstance.appendChild(child)` call.
 *
 * This method happens **in the render phase**. It can mutate `parentInstance` and `child`, but it must not modify any other nodes. It's called while the tree is still being built up and not connected to the actual tree on the screen.
*/

对于react-knova来说

export function appendInitialChild(parentInstance, child) {
  // parentInstance 是 Knova的node
  parentInstance.add(child);
  // parentInstance 绘制,类似于DOM中的操作DOM
  updatePicture(parentInstance);
}

finalizeInitialChildren(instance: Instance, type: Type, props, rootContainer, hostContext):boolean

翻译

最后操作instance的阶段。不像 createInstance,当 finalizeInitialChildren 被调用的时候,所有的子节点被加到了instance,但还是处于离屏的状态。

这个API设计还有一个目的:当这个节点被挂载到屏幕的时候,你可以判断是否需要处理某些工作。当finalizeInitialChildren返回true时,instance会调用commitMount

 /**
  * In this method, you can perform some final mutations on the `instance`. Unlike with `createInstance`, by the time `finalizeInitialChildren` is called, all the initial children have already been added to the `instance`, but the instance itself has not yet been connected to the tree on the screen.
  *
  * This method happens **in the render phase**. It can mutate `instance`, but it must not modify any other nodes. It's called while the tree is still being built up and not connected to the actual tree on the screen.
  *
  * There is a second purpose to this method. It lets you specify whether there is some work that needs to happen when the node is connected to the tree on the screen. If you return `true`, the instance will receive a `commitMount` call later. See its documentation below.
  *
  * If you don't want to do anything here, you should return `false`.
*/

对于react-knova来说

// 不处理 `commitMount`
export function finalizeInitialChildren(domElement, type, props) {
  return false;
}

prepareUpdate(instance: Instance, tye: Type, oldProps, newProps, rootContainer, hostContext): UpdatePayload

翻译

比较oldPropsnewProps,返回一组需要update的对象,这些update会在commitUpdate里被应用到。

/**
  * React calls this method so that you can compare the previous and the next props, and decide whether you need to update the underlying instance or not. If you don't need to update it, return `null`. If you need to update it, you can return an arbitrary object representing the changes that need to happen. Then in `commitUpdate` you would need to apply those changes to the instance.
  *
  * This method happens **in the render phase**. It should only *calculate* the update — but not apply it! For example, the DOM renderer returns an array that looks like `[prop1, value1, prop2, value2, ...]` for all props that have actually changed. And only in `commitUpdate` it applies those changes. You should calculate as much as you can in `prepareUpdate` so that `commitUpdate` can be very fast and straightforward.
  *
  * See the meaning of `rootContainer` and `hostContext` in the `createInstance` documentation.
*/

对于react-knova来说

export function prepareUpdate(domElement, type, oldProps, newProps) {
  return UPDATE_SIGNAL;
}

shouldSetTextContent(type: Type, props: Props): boolean

翻译

某些平台支持直接设置节点的属性,比如 node.textContent。如果返回trueReact会假设这个节点的子节点都是文本,并不会创建节点。

如果你不想做任何处理,返回 false

/**
 * Some target platforms support setting an instance's text content without manually creating a text node. For example, in the DOM, you can set `node.textContent` instead of creating a text node and appending it.
 *
 * If you return `true` from this method, React will assume that this node's children are text, and will not create nodes for them. It will instead rely on you to have filled that text during `createInstance`. This is a performance optimization. For example, the DOM renderer returns `true` only if `type` is a known text-only parent (like `'textarea'`) or if `props.children` has a `'string'` type. If you return `true`, you will need to implement `resetTextContent` too.
  *
  * If you don't want to do anything here, you should return `false`.
  *
  * This method happens **in the render phase**. Do not mutate the tree from it.
*/

对于react-knova来说

export function shouldSetTextContent(type, props) {
  return false;
}

getRootHostContext(rootContainer: Container)

翻译

返回初始上下文 the root of the tree

/**
 * This method lets you return the initial host context from the root of the tree. See `getChildHostContext` for the explanation of host context.
 *
 * If you don't intend to use host context, you can return `null`.
 *
 * This method happens **in the render phase**. Do not mutate the tree from it.
*/

对于react-knova来说

export function getRootHostContext() {
  return NO_CONTEXT;
}

getChildHostContext(parentHostContext: HostContext, type: Type, rootContainer: Container): HostContext

翻译

Host 上下文会帮助我们记录一些信息,这些信息表示你在这棵树的哪里。比如说:DOM renderer利用他去基于是在HTML的tree里,还是在SVG的tree里。

/**
  * Host context lets you track some information about where you are in the tree so that it's available inside `createInstance` as the `hostContext` parameter. For example, the DOM renderer uses it to track whether it's inside an HTML or an SVG tree, because `createInstance` implementation needs to be different for them.
  *
  * If the node of this `type` does not influence the context you want to pass down, you can return `parentHostContext`. Alternatively, you can return any custom object representing the information you want to pass down.
  *
  * If you don't want to do anything here, return `parentHostContext`.
  *
  * This method happens **in the render phase**. Do not mutate the tree from it.
*/

对于react-knova来说

export function getChildHostContext() {
  return NO_CONTEXT;
}

getPublicInstance(instance: Instance | TextInstance): PublicInstance;

翻译

对外二次处理之后的暴露ref,也可以直接返回这个实例。

/**
         * Determines what object gets exposed as a ref. You'll likely want to return the `instance` itself. But in some cases it might make sense to only expose some part of it.
         *
         * If you don't want to do anything here, return `instance`.
*/

对于react-knova来说

export function getPublicInstance(instance) {
  return instance;
}

prepareForCommit(containerInfo: Container): Record<string, any> | null;

翻译

在改变screen节点之前的一些操作。比如说DOM renderer 会保存当前的selection,之后在重新回显回去。与resetAfterCommit相反。

/**
 * This method lets you store some information before React starts making changes to the tree on the screen. For example, the DOM renderer stores the current text selection so that it can later restore it. This method is mirrored by `resetAfterCommit`.
 *
 * Even if you don't want to do anything here, you need to return `null` from it.
*/

对于react-knova来说

export function prepareForCommit() {
  return null;
}

resetAfterCommit(containerInfo: Container): void;

/**
  * This method is called right after React has performed the tree mutations. You can use it to restore something you've stored in `prepareForCommit` — for example, text selection.
  *
  * You can leave it empty.
*/

对于react-knova来说

export function resetAfterCommit() {
  // Noop
}

scheduleTimeout

/**
 * You can proxy this to `setTimeout` or its equivalent in your environment.
*/

对于react-knova来说

export const scheduleTimeout = setTimeout;

cancelTimeout(id: TimeoutHandle): void;

/**
  * You can proxy this to `clearTimeout` or its equivalent in your environment.
*/

对于react-knova来说

export const cancelTimeout = clearTimeout;

appendChild?(parentInstance: Instance, child: Instance | TextInstance): void;

翻译

类似于DOM里的parentInstance.appendChild(child)。在 commit 阶段,但是在这个函数里,你仍然不能操作已经绘制在screen中的节点。如果需要操作,请在commitMount处理。

/**
  * This method should mutate the `parentInstance` and add the child to its list of children. For example, in the DOM this would translate to a `parentInstance.appendChild(child)` call.
  *
  * Although this method currently runs in the commit phase, you still should not mutate any other nodes in it. If you need to do some additional work when a node is definitely connected to the visible tree, look at `commitMount`.
*/

对于react-knova来说

export function appendChild(parentInstance, child) {
  // step1: 处理节点 已有的 => 设置到最上层; 没有的 => add
  if (child.parent === parentInstance) {
    child.moveToTop();
  } else {
    parentInstance.add(child);
  }
  // 批量绘制 
  updatePicture(parentInstance);
}

insertBefore?(parentInstance: Instance, child: Instance | TextInstance, beforeChild: Instance | TextInstance | SuspenseInstance): void;

翻译

类似于DOM里的parentInstance.insertBefore(child, beforeChild).

/**
  * This method should mutate the `parentInstance` and place the `child` before `beforeChild` in the list of its children. For example, in the DOM this would translate to a `parentInstance.insertBefore(child, beforeChild)` call.
  *
  * Note that React uses this method both for insertions and for reordering nodes. Similar to DOM, it is expected that you can call `insertBefore` to reposition an existing child. Do not mutate any other parts of the tree from it.
*/

对于react-knova来说

export function insertBefore(parentInstance, child, beforeChild) {
  // child._remove() will not stop dragging
  // but child.remove() will stop it, but we don't need it
  // removing will reset zIndexes
  child._remove();
  parentInstance.add(child);
  child.setZIndex(beforeChild.getZIndex());
  updatePicture(parentInstance);
}

removeChild?(parentInstance: Instance, child: Instance | TextInstance | SuspenseInstance): void;

翻译

类似于DOM的removeChild

/**
 * This method should mutate the `parentInstance` to remove the `child` from the list of its children.
 *
 * React will only call it for the top-level node that is being removed. It is expected that garbage collection would take care of the whole subtree. You are not expected to traverse the child tree in it.
*/

对于react-knova来说

export function removeChild(parentInstance, child) {
  child.destroy();
  child.off(EVENTS_NAMESPACE);
  updatePicture(parentInstance);
}

commitTextUpdate?(textInstance: TextInstance, oldText: string, newText: string): void;

翻译

这个方法会更新textInstancetextContentnextText

/**
 * This method should mutate the `textInstance` and update its text content to `nextText`.
 *
 * Here, `textInstance` is a node created by `createTextInstance`.
*/

对于react-knova来说

不做处理

commitMount?(instance: Instance, type: Type, props: Props, internalInstanceHandle: OpaqueHandle,): void;

finalizeInitialChildren返回为true的时候,才会被调用。在这个阶段,我们可以节点已经绘制到浏览器中去了,我们可以对这些实际的节点做一些事情。比如对于DOM节点来说,是使某个节点autoFocus

一个理想状态的 commitMount 只需要考虑自己。举个不理想的场景的例子: 节点A在其他节点上注册了一些事件,那么在调用removeChild 函数时,就需要主动注销掉他们。(// TODO: 这些应该怎么翻译呢???)

/**
 * This method is only called if you returned `true` from `finalizeInitialChildren` for this instance.
 *
 * It lets you do some additional work after the node is actually attached to the tree on the screen for the first time. For example, the DOM renderer uses it to trigger focus on nodes with the `autoFocus` attribute.
 *
 * Note that `commitMount` does not mirror `removeChild` one to one because `removeChild` is only called for the top-level removed node. This is why ideally `commitMount` should not mutate any nodes other than the `instance` itself. For example, if it registers some events on some node above, it will be your responsibility to traverse the tree in `removeChild` and clean them up, which is not ideal.
 *
 * The `internalHandle` data structure is meant to be opaque. If you bend the rules and rely on its internal fields, be aware that it may change significantly between versions. You're taking on additional maintenance risk by reading from it, and giving up all guarantees if you write something to it.
 *
 * If you never return `true` from `finalizeInitialChildren`, you can leave it empty.
*/

对于react-knova来说

不做处理

commitUpdate(instance: Instance, updatePayload: UpdatePayload, type: Type, prevProps, nextProps, internalHandle): void

将之前在比较新老props后得到的一组updatePayload,应用到实例中。

/**
 * This method should mutate the `instance` according to the set of changes in `updatePayload`. Here, `updatePayload` is the object that you've returned from `prepareUpdate` and has an arbitrary structure that makes sense for your renderer. For example, the DOM renderer returns an update payload like `[prop1, value1, prop2, value2, ...]` from `prepareUpdate`, and that structure gets passed into `commitUpdate`. Ideally, all the diffing and calculation should happen inside `prepareUpdate` so that `commitUpdate` can be fast and straightforward.
 *
 * The `internalHandle` data structure is meant to be opaque. If you bend the rules and rely on its internal fields, be aware that it may change significantly between versions. You're taking on additional maintenance risk by reading from it, and giving up all guarantees if you write something to it.
*/

对于react-knova来说

export function commitUpdate(
  instance,
  updatePayload,
  type,
  oldProps,
  newProps
) {
  // 比较新老属性,应用到instance上。
  applyNodeProps(instance, newProps, oldProps);
}

感想

利用ReactFiberReconciler创建出来指定某个宿主环境下的节点操作机制,指的component-unit借鉴。component-unit完全可以做到,分别在PC\H5\Canvas 上传不同的HostConfig实现不同的fieldType渲染。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.