Skip to content

中间件

¥Middleware

改变浮动元素位置的对象,作为队列按顺序执行。

¥Objects that change the positioning of the floating element, executed in order as a queue.

中间件允许你自定义定位的行为,并根据需要进行细化,添加你自己的自定义逻辑。

¥Middleware allow you to customize the behavior of the positioning and be as granular as you want, adding your own custom logic.

computePosition() 从通过 placement 的初始定位开始 - 然后中间件作为初始放置计算和最终返回数据以进行渲染的中间 “middle” 步骤执行。

¥computePosition() starts with initial positioning via placement — then middleware are executed as an in-between “middle” step of the initial placement computation and eventual return of data for rendering.

每个中间件按顺序执行:

¥Each middleware is executed in order:

computePosition(referenceEl, floatingEl, {
  placement: 'right',
  middleware: [],
});

示例

¥Example

const shiftByOnePixel = {
  name: 'shiftByOnePixel',
  fn({x, y}) {
    return {
      x: x + 1,
      y: y + 1,
    };
  },
};

这个(不是特别有用)中间件将 1 像素添加到坐标中。要使用此中间件,请将其添加到 middleware 数组中:

¥This (not particularly useful) middleware adds 1 pixel to the coordinates. To use this middleware, add it to your middleware array:

computePosition(referenceEl, floatingEl, {
  placement: 'right',
  middleware: [shiftByOnePixel],
});

这里,computePosition() 将计算坐标,将浮动元素放置到参考元素的 right 中心,并与之齐平。

¥Here, computePosition() will compute coordinates that will place the floating element to the right center of the reference element, lying flush with it.

然后执行中间件,导致这些坐标移动一个像素。然后返回该数据进行渲染。

¥Middleware are then executed, resulting in these coordinates getting shifted by one pixel. Then that data is returned for rendering.

形状

¥Shape

中间件是具有 name 属性和 fn 属性的对象。fn 属性提供了中间件的逻辑,它返回新的定位坐标或有用的数据。

¥A middleware is an object that has a name property and a fn property. The fn property provides the logic of the middleware, which returns new positioning coordinates or useful data.

数据

¥Data

任何数据都可以通过从 fn 返回的对象的可选 data 属性传递。消费者可以通过 middlewareData 属性访问:

¥Any data can be passed via an optional data property of the object that is returned from fn. This will be accessible to the consumer via the middlewareData property:

const shiftByOnePixel = {
  name: 'shiftByOnePixel',
  fn({x, y}) {
    return {
      x: x + 1,
      y: y + 1,
      data: {
        amount: 1,
      },
    };
  },
};
computePosition(referenceEl, floatingEl, {
  middleware: [shiftByOnePixel],
}).then(({middlewareData}) => {
  console.log(middlewareData.shiftByOnePixel);
});

函数

¥Function

你可能会注意到,Floating UI 封装的中间件实际上是函数。这样你就可以传递选项,更改中间件的行为方式:

¥You may notice that Floating UI’s packaged middleware are actually functions. This is so you can pass options in, changing how the middleware behaves:

const shiftByAmount = (amount = 0) => ({
  name: 'shiftByAmount',
  options: amount,
  fn: ({x, y}) => ({
    x: x + amount,
    y: y + amount,
  }),
});

它返回一个对象并使用闭包来传递配置的行为:

¥It returns an object and uses a closure to pass the configured behavior:

const middleware = [shiftByAmount(10)];

中间件对象上的 options 键保存依赖,允许深度比较反应性。

¥The options key on a middleware object holds the dependencies, allowing deep comparison reactivity.

始终返回对象

¥Always return an object

fn 内确保返回一个对象。它不需要包含属性,但提醒你它应该是纯的,你必须返回一个对象。切勿改变从 fn 传入的任何值。

¥Inside fn make sure to return an object. It doesn’t need to contain properties, but to remind you that it should be pure, you must return an object. Never mutate any values that get passed in from fn.

MiddlewareState

一个对象被传递到 fn,其中包含有关正在执行的中间件生命周期的有用数据。

¥An object is passed to fn containing useful data about the middleware lifecycle being executed.

在前面的示例中,我们从 fn 参数对象中解构了 xy。这些只是传递到中间件的两个属性,但还有更多。

¥In the previous examples, we destructured x and y out of the fn parameter object. These are only two properties that get passed into middleware, but there are many more.

传递的属性如下:

¥The properties passed are below:

interface MiddlewareState {
  x: number;
  y: number;
  initialPlacement: Placement;
  placement: Placement;
  strategy: Strategy;
  middlewareData: MiddlewareData;
  elements: Elements;
  rects: ElementRects;
  platform: Platform;
}

x

这是浮动元素的 x 轴坐标。

¥This is the x-axis coordinate to position the floating element to.

y

这是浮动元素的 y 轴坐标。

¥This is the y-axis coordinate to position the floating element to.

elements

这是一个包含引用和浮动元素的对象。

¥This is an object containing the reference and floating elements.

rects

这是一个包含参考元素和浮动元素的 Rect 的对象,形状为 {width, height, x, y} 的对象。

¥This is an object containing the Rects of the reference and floating elements, an object of shape {width, height, x, y}.

middlewareData

这是一个包含生命周期当前步骤中任何中间件的所有数据的对象。生命周期在 middleware 数组上循环,因此后面的中间件可以访问之前运行的任何中间件的数据。

¥This is an object containing all the data of any middleware at the current step in the lifecycle. The lifecycle loops over the middleware array, so later middleware have access to data from any middleware run prior.

strategy

定位策略。

¥The positioning strategy.

initialPlacement

初始(或首选)安置传递至 computePosition()

¥The initial (or preferred) placement passed in to computePosition().

placement

有状态的结果放置。像 flip 这样的中间件将 initialPlacement 更改为新的中间件。

¥The stateful resultant placement. Middleware like flip change initialPlacement to a new one.

platform

包含使 Floating UI 在当前平台上工作的方法的对象,例如 DOM 或 React Native。

¥An object containing methods to make Floating UI work on the current platform, e.g. DOM or React Native.

排序

¥Ordering

中间件在数组中的放置顺序很重要,因为中间件使用从先前的中间件返回的坐标。这意味着它们根据当前的定位状态执行工作。

¥The order in which middleware are placed in the array matters, as middleware use the coordinates that were returned from previous ones. This means they perform their work based on the current positioning state.

中间件数组中的三个 shiftByOnePixel 意味着坐标总共移动了 3 个像素:

¥Three shiftByOnePixel in the middleware array means the coordinates get shifted by 3 pixels in total:

const shiftByOnePixel = {
  name: 'shiftByOnePixel',
  fn: ({x, y}) => ({x: x + 1, y: y + 1}),
};
const middleware = [
  shiftByOnePixel,
  shiftByOnePixel,
  shiftByOnePixel,
];

如果后来的 shiftByOnePixel 实现具有基于 xy 的当前值的条件,则条件可以根据它们在数组中的位置而改变。

¥If the later shiftByOnePixel implementations had a condition based on the current value of x and y, the condition can change based on their placement in the array.

理解这一点有助于了解中间件的放置顺序,因为将中间件放置在另一个中间件之前或之后可能会产生不同的结果。

¥Understanding this can help in knowing which order to place middleware in, as placing a middleware before or after another can produce a different result.

一般来说,offset() 应该始终位于中间件数组的开头,而 arrow()hide() 则位于末尾。其他核心中间件可以根据所需的行为进行调整。

¥In general, offset() should always go at the beginning of the middleware array, while arrow() and hide() at the end. The other core middleware can be shifted around depending on the desired behavior.

const middleware = [
  offset(),
  // ...
  arrow({element: arrowElement}),
  hide(),
];

重置生命周期

¥Resetting the lifecycle

有些用例需要重置中间件生命周期,以便其他中间件执行新的逻辑。

¥There are use cases for needing to reset the middleware lifecycle so that other middleware perform fresh logic.

  • flip()autoPlacement() 更改放置时,它们会重置生命周期,以便其他基于当前 placement 修改坐标的中间件不会执行过时的逻辑。

    ¥When flip() and autoPlacement() change the placement, they reset the lifecycle so that other middleware that modify the coordinates based on the current placement do not perform stale logic.

  • size() 使用新应用的维度重置生命周期,因为许多中间件读取维度来执行其逻辑。

    ¥size() resets the lifecycle with the newly applied dimensions, as many middleware read the dimensions to perform their logic.

  • inline() 将引用矩形更改为自定义实现时,它会重置生命周期,类似于 虚拟元素

    ¥inline() resets the lifecycle when it changes the reference rect to a custom implementation, similar to a Virtual Element.

为此,请将 reset 属性添加到从 fn 返回的对象中。

¥In order to do this, add a reset property to the returned object from fn.

type Reset =
  | true
  | {
      placement?: Placement;
      // `true` will compute the new `rects` if the
      // dimensions were mutated. Otherwise, you can
      // return your own new rects.
      rects?: true | ElementRects;
    };
const middleware = {
  name: 'middleware',
  fn() {
    if (someCondition) {
      return {
        reset: {
          placement: nextPlacement,
        },
      };
    }
 
    return {};
  },
};

通过执行此操作,将保留提供给 middlewareData 的数据,因此你可以在重置生命周期后随时读取它。

¥Data supplied to middlewareData is preserved by doing this, so you can read it at any point after you’ve reset the lifecycle.