排名栏的Qwik景观
#javascript #qwik #framework #d3

介绍

本文是我上一篇文章"Tasty Recipes for React & D3. The Ranking Bar."在这篇新文章中的续集,我将完全描述相同的任务,但是我将改变常见和流行的对完全不同的事物的反应。这就是为什么我强烈建议阅读上一篇文章。

前一段时间,我面对了一个新框架。我问自己...这是框架动物园中的新动物吗?我在其中工作了25年以上,一直在等待“奇迹”。它是前端,后端,新的编程语言还是DBM都没关系。我正在尝试为每个新趋势应用程序回答以下问题。这个应用会成为主流吗?一方面,我记得很多故事,例如“ Angular”,“ React”,“ Nodejs”,“ Golang”,“ Postgres”,“ Microsoft C ++”,和“ Foxpro”。另一方面,我记得另一组故事:“骨干”,“ D编程语言”,“ Polymer”,“ OrientDB”,“ PowerSoft Power Builder”和“ Microsoft Site Sert Server” ...我希望您猜测什么是什么这些列表之间的区别是。当然,我不想从第二列表中贬低软件。但是其中一些软件注定要比其他软件更受欢迎。

所以,遇到一个新的框架qwik!我喜欢预测,但我并不神奇。我不知道Qwik是否会在不久的将来吸引开发人员的思想。尽管如此,Qwik看起来像是一个非常透视的框架。 “重新构想边缘的框架” - 告诉我们主页。我喜欢这种方法。我喜欢当作者拒绝任何烦人的遗产并根据以前的经验从头开始启动该项目时。此外,性能是Qwik的Doppelganger。听起来很兴奋!

通常,Qwik上的编码几乎可以接近React,这使许多开发人员从React Society中获得了。尽管Qwik概念有些不同。与其他框架不同,Qwik是可以重新启动的,这意味着QWIK应用程序需要0水合。这允许Qwik应用具有即时交互性,无论大小或复杂性如何。老实说,我的文章主要用于React开发人员。但是,如果您不是反应人,请不要担心。挖掘official resource更加详细。如果您是React Guy,并且想立即从练习开始,那么Qwik Components Concept将非常有用。该文章的主要目标是说明如何使用该框架。这就是为什么我不会像官方文档那样提供QWIK技术知识的原因。我的目标是通过链接和示例指导您在Qwik世界中。与上一篇文章不同,我在示例中使用了Typescript,因为默认情况下在QWIK中使用了此语言。另外,我以下示例还没有准备好生产。这就是为什么不那么批评他们的原因:)尤其是“@ts-ignore”。

我只想告诉您向前看。我基于QWIK的代码比基于React的代码更优雅!这一事实可能对学习和使用Qwik是一个很好的影响。

引导

Stackblitz

我喜欢Stackblitz作为云原型工具。它具有许多不同的预设,例如Angular,React等。但是它没有QWIK预设,因为该框架太年轻。尽管如此,我找到了以下自定义起动器:https://stackblitz.com/edit/qwik-starter?file=README.md

我要解释您应该修改与Qwik和D3一起工作的方法。

首先,我们需要在package.json中更改一个依赖性。

来自

"@builder.io/qwik": "^0.15.2",


"@builder.io/qwik": "^0.16.1",

其次,我们需要安装以下新依赖项。

"@types/d3": "7.4.0",
"d3": "^7.8.0",

Qwik迫使我们遵循特定的惯例。请阅读有用的信息here

实际上,我们在这里谈论的比Qwik还要多。在这个项目中,我使用Qwik City。我们称其为Qwik的元框架。 Qwik City是Qwik,Next.js要反应,Nuxt是什么,或者Sveltekit到Svelte。

我所有未来的活动都将与这些惯例有关。

传统的自举

当然,我在这里使用Stackblitz只是为了互动说明我的想法。在现实生活中,您需要使用另一种方法进行项目自举。幸运的是,Qwik拥有完美的Bootstrapper。如果要启动一个新项目,请运行以下命令。

npm create qwik@latest

请阅读Getting Started Qwik

首先划痕

我更喜欢通过较小的顺序示例来解释更复杂的想法。这就是为什么在我们进行排名栏之前,我想提供一个更简单的示例,我们将来将对目标进行修改。在第一步中,我们需要获取通过D3显示以下信息的应用。

Image description

首先,您可以在here下方找到解决方案。

该应用程序应重新计算并重新绘制每个窗口大小的尺寸值。

让我们从routes文件夹中删除所有内容,然后放置以下index.tsx

import { component$ } from "@builder.io/qwik";
import App from "../components/app";

export default component$(() => <App />);

只有一个index.tsx表示我们仅使用一条“根”路线。

现在我们需要清洁components文件夹。

put app.tsxcomponents文件夹中包含App组件。

import { component$ } from "@builder.io/qwik";
import Chart from "./chart";

export default component$(() => <Chart />);

以下文件chart.tsx包含Chart组件。

import {
  component$,
  useStore,
  useClientEffect$,
  useSignal,
  useOnWindow,
  useTask$,
  $,
} from "@builder.io/qwik";
import * as d3 from "d3";
import { setSvgDimension } from "./utils";

export default component$(() => {
  const store = useStore({ width: 0, height: 0 });
  const svgRef = useSignal<Element>();

  useClientEffect$(() => {
    setSvgDimension(svgRef, store);
  });

  useOnWindow(
    "resize",
    $(() => {
      setSvgDimension(svgRef, store);
    })
  );

  useTask$(({ track }: { track: Function }) => {
    track(() => store.width);
    track(() => store.height);
    render(svgRef, store.width, store.height);
  });

  return <svg class="chart" ref={svgRef} />;
});

export function render(svgRef: any, width: number, height: number) {
  d3.select(svgRef.value).select(".dimenstion-text").remove();

  const svg = d3
    .select(svgRef.value)
    .append("svg")
    .attr("width", width)
    .attr("height", height)
    .append("g")
    .attr("transform", "translate(0,0)");

  svg
    .append("text")
    .text("Hello Qwik!")
    .attr("x", 10)
    .attr("y", 50)
    .attr("width", 200)
    .attr("fill", "red");

  svg
    .append("text")
    .text(`Width = ${width}px | Height = ${height}px`)
    .attr("class", "dimenstion-text")
    .attr("x", 10)
    .attr("y", 80)
    .attr("width", 200)
    .attr("fill", "black");
}

另外,您可以在utils.ts中找到setSvgDimension代码。

import { Signal } from "@builder.io/qwik";

export function setSvgDimension(
  svgRef: Signal<Element | undefined>,
  store: any
) {
  if (svgRef?.value) {
    const { width, height } = svgRef.value.getBoundingClientRect();
    store.width = width;
    store.height = height;
  }
}

让我评论一些要点。

  • 如上一篇文章的示例中,组件返回SVG。
return <svg class="chart" ref={svgRef} />;
  • useSignal允许我们与上面的元素一起工作。
const svgRef = useSignal<Element>();

您可以找到有关useSignal here

的更多信息
  • 根据https://qwik.builder.io/tutorial/hooks/use-client-effect/ Use useClientEffect$() to execute code after the component is resumed. This is useful for setting up timers or streams on the client when the application is resumed。在我的示例中,以下代码设置了组件尺寸并将其放在商店中。
useClientEffect$(() => {
  setSvgDimension(svgRef, store);
});

在这种情况下,useClientEffect$行为类似于React中的以下代码。

useEffect(() => {
  // init the component here...
}, []);
  • useOnWindow / useOn() / useOnDocument()是与相关听众合作的有​​力方法。在下面的代码片段中,我们使用useOnWindow来收听每个窗口大小的更改。
useOnWindow(
  "resize",
  $(() => {
    setSvgDimension(svgRef, store);
  })
);

您可以找到有关here上方的钩子的更多信息。

  • 以下代码行向我们展示了如何存储QWIK跟踪变量。
const store = useStore({ width: 0, height: 0 });
  • 以下代码允许跟踪相关的存储变量更改。
useTask$(({ track }: { track: Function }) => {
  track(() => store.width);
  track(() => store.height);
  // new render when window size has changed
  render(svgRef, store.width, store.height);
});

您可以找到有关上述方法的更多信息:useTask$useStore

我想将useStoreuseTask$与React useStateuseEffect钩进行比较。但是请记住,Qwik不同!

  • render的主要目标是显示每个窗口尺寸的组件宽度和高度。

只是提醒您,您可以在here上方找到示例。

排名栏

正如我一开始告诉您的那样,本文是我上一篇文章"Tasty Recipes for React & D3. The Ranking Bar."的续集。您可以在此处找到所有相关信息。这就是为什么我现在想立即获取和评论我的排名栏的Qwik版本。

传统上,您可以查看完整的解决方案here

让我们专注于发生的变化...

app.tsx

import { component$ } from "@builder.io/qwik";
import Chart from "./chart";

export const data = {
  Apple: 100,
  Apricot: 200,
  Araza: 5,
  Avocado: 1,
  Banana: 150,
  // ...
  Feijoa: 11,
  Fig: 0,
};

// Just add a new prop "data"
export default component$(() => <Chart data={data} />);

utils.ts

import * as d3 from "d3";
import { Signal } from "@builder.io/qwik";

// no changes in comparing with the previous article except for typings
export function dotme(texts: d3.Selection<SVGElement, {}, HTMLElement, any>) {
  texts.each(function () {
    // @ts-ignore
    const text = d3.select(this);
    const chars = text.text().split("");

    let ellipsis = text.text(" ").append("tspan").text("...");
    // @ts-ignore
    const minLimitedTextWidth = ellipsis.node().getComputedTextLength();
    ellipsis = text.text("").append("tspan").text("...");

    const width =
      // @ts-ignore
      parseFloat(text.attr("width")) - ellipsis.node().getComputedTextLength();
    const numChars = chars.length;
    const tspan = text.insert("tspan", ":first-child").text(chars.join(""));

    if (width <= minLimitedTextWidth) {
      tspan.text("");
      ellipsis.remove();
      return;
    }

    // @ts-ignore
    while (tspan.node().getComputedTextLength() > width && chars.length) {
      chars.pop();
      tspan.text(chars.join(""));
    }

    if (chars.length === numChars) {
      ellipsis.remove();
    }
  });
}

// add related types
export interface ChartData {
  [key: string]: number;
}

export interface NormalizedChartRecord {
  fruit: string;
  value: number;
  x: number;
  width: number;
}

// no changes in comparing with the previous article except for typings
export function getNormalizedData(
  data: any,
  width: number
): NormalizedChartRecord[] {
  const tmpData: any[] = [];
  let total = 0;
  for (const key of Object.keys(data)) {
    if (data[key] > 0) {
      tmpData.push({ fruit: key, value: data[key] });
      total += data[key];
    }
  }
  tmpData.sort((a, b) => b.value - a.value);
  let x = 0;
  for (const record of tmpData) {
    const percent = (record.value / total) * 100;
    const barwidth = (width * percent) / 100;
    record.x = x;
    record.width = barwidth;
    x += barwidth;
  }
  return tmpData;
}

export function setSvgDimension(
  svgRef: Signal<Element | undefined>,
  store: any
) {
  if (svgRef?.value) {
    const { width, height } = svgRef.value.getBoundingClientRect();
    store.width = width;
    store.height = height;
  }
}

,最后是chart.tsx。请在代码中阅读我的评论。

import {
  component$,
  useStore,
  useClientEffect$,
  useSignal,
  useOnWindow,
  useTask$,
  $,
} from "@builder.io/qwik";
import * as d3 from "d3";
import { ChartData, dotme, getNormalizedData, setSvgDimension } from "./utils";

export interface ChartProps {
  data: ChartData;
}

export default component$(({ data }: ChartProps) => {
  // store width and height of the component here
  const store = useStore({ width: 0, height: 0 });
  // control the SVG container
  const svgRef = useSignal<Element>();

  // initialization
  useClientEffect$(() => {
    // update the store
    setSvgDimension(svgRef, store);
  });

  // listen window size changes
  useOnWindow(
    "resize",
    $(() => {
      // update the store
      setSvgDimension(svgRef, store);
    })
  );

  // track width and height
  useTask$(({ track }: { track: Function }) => {
    track(() => store.width);
    track(() => store.height);
    // alter that, get normalized data
    const normalizedData = getNormalizedData(data, store.width);
    // and, finally, render the component according the new screen size
    render(normalizedData, svgRef, store.width, store.height);
  });

  return <svg class="chart" ref={svgRef} />;
});

// the following code is close to the related one in the previous article
export function render(
  normalizedData: any,
  svgRef: any,
  width: number,
  height: number
) {
  const svg = d3
    .select(svgRef.value)
    .append("svg")
    .attr("width", width)
    .attr("height", height)
    .append("g")
    .attr("transform", "translate(0,0)");

  const color = d3
    .scaleOrdinal()
    .domain(Object.keys(normalizedData))
    .range(d3.schemeTableau10);

  svg
    .selectAll()
    .data(normalizedData)
    .enter()
    .append("g")
    .append("rect")
    .attr("x", (d: any) => d.x)
    .attr("width", (d: any) => d.width - 1)
    .attr("y", 0)
    .attr("height", 50)
    // @ts-ignore
    .attr("fill", (_: any, i: number) => color(i));

  svg
    .selectAll("text")
    .data(normalizedData)
    .join("text")
    .text((d: any) => d.fruit)
    .attr("x", (d: any) => d.x + 5)
    .attr("y", (d: any) => 30)
    .attr("width", (d: any) => d.width - 1)
    .attr("fill", "white");

  // @ts-ignore
  svg.selectAll("text").call(dotme);
}

现在,让我们运行示例并重新增加窗口大小:https://stackblitz.com/edit/qwik-starter-nkdscu?file=src%2Fcomponents%2Fchart.tsx

感谢您的关注,Qwik学习!