Search
Write a publication
Pull to refresh
3
0.1
Send message

Типа до сих пор этого не произошло?

Так вы же его буквально задаете - поэтому это литерал.

А в каких случаях это `[block1Ref.current, block2Ref.current] ` имеет смысл?

Доступность — это не «фишка» и не «дополнительная опция». Это базовое качество современного веб-приложения. Если ваш интерфейс не работает с клавиатурой или не читается экранными читалками, значит, он недоступен для миллионов пользователей

Полностью согласен.

А такой вопрос есть, вы сталкивались с тем, что бы для тестирования вам необходимо было еще все это обвешивать тестовыми идишниками?

Просто для примера, я смотрю на ваш пример и что-то с лету в голову не приходит кейса, когда что-то подобное было нужно...

К слову о ненужных зависимостях, моя практика утверждает обратное: если их удалить, то линтер и правда не ругается, но высота третьего блока не будет обновляться динамически.

Вы про пример?

Не совсем поняла эту часть — «когда работаешь с хуками нельзя конфигурировать их литералами». Уточните, пожалуйста.

Вы в зависимости useLayout прокинули литерал, поэтому у вас в примере при каждом рендере все это устанавливалось и снималось постоянно

Я бы на вашем месте пример все таки отредактировал. Вдруг кто-то подумает, что у вас что-то такое в продакшене.

Тут два момента. Первый, когда работаешь с рефами нет никакого смысла прокидывать их в зависимости (у вас этого конечно нет, но ваше решение вдохновлено чем-то подобным). Линтеры об этом знают, поэтому "кричать" не будут. И второй, когда работаешь с хуками нельзя конфигурировать их объектными литералами. В вашем примере, каждый рендер приводит к отработке useLayoutEffect, что вредно.

И есть еще один момент. Вы сделали вот такую штуку:

const [hb1, setHB1] = useState(0);
const [hb2, setHB2] = useState(0); 

Это означает, что при каждом изменении размера у вас перерисовывается весь компонент. Это, конечно, мелочь, если размеры меняются редко, но все таки смысла для ряда случаев в этом нет никакого.

п.с.: за оптимизм пальчик вверх я вам поставлю.

Так я же написал почему. Мы имеем число 0 для которого Boolean(0) возвращает false. Иными словами Boolean только что отсеял значение с типом number. Т.е. для number Boolean не является корректным предикатом типа.

Но вообще, я выше описал общий случай почему Boolean в принципе не является предикатом типа.

Если более обще посмотреть. То имеем:

interface BooleanConstructor {
    new (value?: any): Boolean;
    <T>(value?: T): boolean;
    readonly prototype: Boolean;
}

Иными словами тут всегда выход - это boolean, а для предиката типов должна быть ясная связь между типом входного значения и типом выходного. Поэтому при использовании Boolean и возможны ошибки типа той, что в примере из статьи.

По моему, стоит задиприкейтить {} потому что сейчас это пережиток прошлого.

В этом примере мы ожидаем, что тип arr будет number[], но на самом деле получаем (number | undefined)[], потому что TypeScript не может точнее определить тип возвращаемого значения. Это неудобно: ты точно знаешь, что в рантайме undefined не будет, но в типах обязан это учитывать

Нет. Все обстоит не так. Начиная с версии 5.5 Typescript корректно выводит предикаты типов. Но должны соблюдаться два условия:

1. Типа предикат должен возвращать тип Т, когда функция возвращает true

2. Когда функция возвращает false входной параметр точно не Т

А что мы видимо в предложенном примере? Когда у нас значение 0, то Boolean возвращает false. Поэтому Boolean не является корректным предикатом типов для number. Поэтому typescript и не меняет number | undefined на number. И правильно делает, потому что тогда бы разработчики допускали ошибку в коде.

После того как в начале дан пример в котором варианты 1. это селектор и 1. это уникальный селектор представляется, что статья морально устарела лет этак на 5 минимум.

Ролевые селекторы позволяют искать элементы по их роли на странице, что делает их более стабильными и предсказуемыми, однако, этого все ещё недостаточно.

Одной из основных проблем с использованием ролевых селекторов является их ограниченность. Они не могут быть использованы для поиска элементов по их атрибутам или содержимому, что может привести к непредсказуемым результатам.

Также, у них есть всё та же проблема, что и у классовых селекторов - мы можем наткнуться на коллизию элементов.

Ролевые селекторы позволяют искать элементы так как если бы их искал человек. Поэтому, если смотреть с этой стороны, то получается следующее. Второй абзац в принципе не нужен, т.к. элемент и не должен искаться по атрибуту. А вот про содержимое я не понял.

И второе, коллизия элементов говорит о том, что с точки зрения семантики элемент спроектирован плохо, поэтому коллизия и возникает. Иными словами возникает ситуация когда ассистивная технология не может один элемент отличить от другого.

Поэтому, основной проблемой мне видится высокий порог вхождения для использования таких селекторов, а вовсе не их ограниченность.

Я имею в виду, что resolve типизирован? Т.е. если ему передать строку 'profile', то во первых он подскажет саму строку, а во вторых предложит объект параметров, который соответствует данному маршруту?

А как это работает на ТС, можно ссылку?

А то я не очень понимаю, если вы импортируете из пакета resolve, то кто следит за типизацией параметров?

resolve('profile', user_id=123)

Где происходит связывание псевдонима и соответствующего ему типа объекта параметров? Из обозначенного вами кода я не очень понял как это все типизируется из коробки?

Я использовал роутеровский PathParam. Что-то типа:

Record<PathParam<{your_pattern}>, string>

И так как это все равно на выходе всегда строка не перегружал типами.

Любопытно получилось. Было интересно.

Я когда столкнулся с похожей проблемой пошел немного другим путем. Но в основе тоже решил сделать дерево маршрутов.

А вот генерировать его решил из конфигурации:

Скрытый текст
const routesConfig = {
  root: {
    pattern: '/<public_url_here>',
    subRoutes: {
      categoryOne: '/path_CategoryOne',
      categoryWithSubs: {
        pattern: '/categoryWithSubs/:param1',
        subRoutes: {
          subCut1: '/subCut/:param2',
          subCut2: '/subCut/:param3'
        }
      }
    }
  }
} as const

const { root } = createRoute(routesConfig)

expect(root.pattern).toBe('/<public_url_here>')
expect(root.categoryOne.pattern).toBe('/<public_url_here>/path_CategoryOne')
expect(root.categoryWithSubs.getPath({ param1: 'xx1' })).toBe('/<public_url_here>/categoryWithSubs/xx1')
expect(root.categoryWithSubs.subCut1.pattern).toBe('/<public_url_here>/categoryWithSubs/:param1/subCut/:param2')
expect(root.categoryWithSubs.subCut1.getPath({ param1: 'xx1', param2: 'xx2' })).toBe('/<public_url_here>/categoryWithSubs/xx1/subCut/xx2')

// const { param1, param2 } = routes.categoryWithSubs.subCut1.useParams()
/*
<Route path={root.categoryWithSubs.getPath()} Component={...}>
  <Route path={root.categoryWithSubs.subCut1.routePattern} Component={...} />
  <Route path={root.categoryWithSubs.subCut2.routePattern} Component={...} />
</Route>
*/

Разумеется получилось сильно проще чем у вас. Плюс я отталкивался от утилит типов и духа самого реакт роутера, поэтому не стал добавлять того, чего там нет. И типы ослаблены примерно так же как и у самого React Route. Но у меня и не стояло задачи поддерживать огромное количество страниц как в вашем случае.

п.с.: код написал, что бы схематично обозначить сам подход. Он может быть в чем-то не до конца точен.

А вы зачем все это сделали в таком виде? Т.е. зачем тут все эти развернутые исходники? Очень неудобно все это читать, но больше всего непонятно именно зачем?

Я рандомно, листая, остановился на одном из таких кусков и не очень понял зачем это показывать. У статьи же явно какие то другие цели, чем обсуждать это. Или все таки не против обсудить?

Пример:

Скрытый текст
// Graph/Click.tsx
import { useCallback, useState } from 'react'
import ForceGraph, { type NodeObject } from 'react-force-graph-2d'
import { Flex } from '../Flex'
import { defaultColors, drawNodeRing, generateGraphData } from './utils'

const graphData = generateGraphData()

function Click() {
  // Выделенные узлы
  const [clickNodes, setClickNodes] = useState<(NodeObject | null)[]>([])
  // Индикатор выделения нескольких узлов
  const [multiple, setMultiple] = useState(false)

  const handleNodeClick = useCallback(
    (node: NodeObject) => {
      if (!multiple) {
        setClickNodes([node])
        return
      }
      let newClickNodes = [...clickNodes]
      if (newClickNodes.includes(node)) {
        newClickNodes = newClickNodes.filter((n) => n !== node)
      } else {
        newClickNodes.push(node)
      }
      setClickNodes(newClickNodes)
    },
    [clickNodes, multiple],
  )

  const { nodeColor, activeNodeColor } = defaultColors

  return (
    <Flex flexDirection='column' gap={12}>
      <h3>Выделение узлов</h3>
      <fieldset>
        <legend>Настройки</legend>
        <label>
          <input
            type='checkbox'
            checked={multiple}
            onChange={(e) => setMultiple(e.target.checked)}
          />{' '}
          Выделение нескольких вершин
        </label>
      </fieldset>
      <Flex width={768} height={480} border='1px dashed rgba(0,0,0,0.25)'>
        <ForceGraph
          width={768}
          height={480}
          graphData={graphData}
          onNodeClick={handleNodeClick}
          nodeCanvasObject={(node, ctx) => drawNodeRing({ node, ctx })}
          // Рисуем кольцо вокруг выделенных узлов
          nodeCanvasObjectMode={(node) =>
            clickNodes.includes(node) ? 'before' : undefined
          }
          // При клике по фону очищаем выделенные узлы
          onBackgroundClick={() => {
            setClickNodes([])
          }}
          nodeColor={(node) =>
            clickNodes.includes(node) ? activeNodeColor : nodeColor
          }
          // Отключаем перетаскивание узлов
          enableNodeDrag={false}
        />
      </Flex>
    </Flex>
  )
}

export default Click

Первый же вопрос по форме, почему это называется Click?

Дальше, просто...

1. Если у вас ForceGraph мэм-ный компонент, то в useCallback нужно оборачивать все, а не один handleNodeClick. В этом просто нет никакого смысла.

2. Так как у вас ForceGraph не зависит от clickNodes, и параметризуется только обработчиками то получается Реакту вообще нет никакого смысла постоянно перерисовывать/пересчитывать этот компонент при изменении обработчиков. Он должен быть отрисован один раз, а потом уже заниматься своей работой никак не перерисовывая постоянно ваш Click

3. Все для чего нужен перерендер Реактом - это работа с элементом формы, чекбосом. Поэтому делать clickNodes состоянием компонента нет никакого смысла и даже вредно. Его изменения не должны влиять на рендер.

4. При работе с управляемым чекбоксом лезть в объект события нет никакой необходимости. Поэтому если есть желание тоже можно добавить обработчик через useCallback. А можно и не добавлять, т.к. в вашем случае чекбокс не библиотечный компонент.

Скрытый текст
// Graph/Click.tsx
import { useCallback, useState } from 'react'
import ForceGraph, { type NodeObject } from 'react-force-graph-2d'
import { Flex } from '../Flex'
import { defaultColors, drawNodeRing, generateGraphData } from './utils'

const graphData = generateGraphData()

function Click() {
  // Выделенные узлы
  const clickNodes = useRef<(NodeObject | null)[]>([])
  // Индикатор выделения нескольких узлов
  const [multiple, setMultiple] = useState(false)
  const isMultiple = useRef(multiple)

  const handleNodeClick = useCallback(
    (node: NodeObject) => {
      if (!isMultiple.current) {
        clickNodes.current = [node]
        return
      }

      let workIndex = clickNodes.current.indexOf(node)
      
      if (workIndex >= 0) {
        clickNodes.current.splice(workIndex, 1)
      } else {
        clickNodes.current.push(node)
      }
    },
    [],
  )

  const nodeCanvasObjectHandle = useCallback((node, ctx) => drawNodeRing({ node, ctx }), [])
  const nodeCanvasObjectModeHandle = useCallback((node) => clickNodes.current.includes(node) ? 'before' : undefined, [])
  const onBackgroundClick = useCallback(() => { clickNodes.current = [] }, [])
  const nodeColorHandle = useCallback((node) => clickNodes.current.includes(node) ? activeNodeColor : nodeColor, [])
  const onChangeMultiple = useCallback(() => {
    setMultiple(v => {
      const newValue = !v

      isMultiple.current = newValue

      return newValue
    })
  }, [])

  const { nodeColor, activeNodeColor } = defaultColors

  return (
    <Flex flexDirection='column' gap={12}>
      <h3>Выделение узлов</h3>
      <fieldset>
        <legend>Настройки</legend>
        <label>
          <input
            type='checkbox'
            checked={multiple}
            onChange={onChangeMultiple}
          />{' '}
          Выделение нескольких вершин
        </label>
      </fieldset>
      <Flex width={768} height={480} border='1px dashed rgba(0,0,0,0.25)'>
        <ForceGraph
          width={768}
          height={480}
          graphData={graphData}
          onNodeClick={handleNodeClick}
          nodeCanvasObject={nodeCanvasObjectHandle}
          // Рисуем кольцо вокруг выделенных узлов
          nodeCanvasObjectMode={nodeCanvasObjectModeHandle}
          // При клике по фону очищаем выделенные узлы
          onBackgroundClick={onBackgroundClick}
          nodeColor={nodeColorHandle}
          // Отключаем перетаскивание узлов
          enableNodeDrag={false}
        />
      </Flex>
    </Flex>
  )
}

export default Click

В результате мы получили компонент, которые перерисовывается только по чекбоксу. Что тоже можно убрать, например, добавив контекст.

И вот, возвращаемся к тому с чего я начал. Я вот не очень понимаю, зачем было выкладывать такое? Лучше же просто расписать человеческим языком и все. Отвлекаться на такой код и тем более тратить на него место, по моему, контрпродуктивно. Или все таки была цель показать как именно Реакт использовать для таких задач?

Не-е, это я вставил, меня отвлекли и я упустил возможность исправить. Разумеется там не должно быть никакого length.

for (let i = 0, max = array.length; i < max; i++) {
  console.log(array[i]);
}

{
  const max = array.length
  for (let i = 0; i < max; i++) {
    console.log(array[i]);
  }
}


А мне еще другое любопытно. Описанный пример загрязняет область видимости переменными цикла. Что не очень удобно, если имеются несколько, например, идущих подряд циклов. И что тогда автор делает, придумывает разные имена или использует какой-нибудь такой подход, что бы сохранить оптимизацию производительности:

for (let i = 0, max = array.length; i < length; i++) {
  console.log(array[i]);
}

или

{
  const max = array.length
  for (let i = 0, max = length; i < length; i++) {
    console.log(array[i]);
  }
}


Не знаю. Когда слышу всякое про х10 производительности там, много дешевой колбасы что бы "накормить" десятки тысяч тут, и всякое про нужно для резюме и важность количества над качеством сразу представляется в первую очередь работа по "перекладыванию json-ов".

Ну, во-первых, никто и не спрашивает согласия-то.

Да нет, я вопрос поставил именно так, что нужно именно согласие и понимание, что усилия идут на дешевую работу. То, что появился инструмент который может создавать много дешевой дряни вовсе не означает, что это нужно делать.

Во-вторых, эти "5-6 раз” могут быть от 2 до 10 на самом деле. И если ты инструмент освоил хорошо, то можешь скакнуть в продуктивности повыше, и тогда денег на час занятости у тебя будет больше(ну или больше свободного времени), а если освоил плохо, то да, твой час внезапно подешевел.

Это же не так работает. Обычный работник работает за оклад. В редких случаях еще есть премия. Все. Если ваша работа упрощается настолько, что ее может делать ИИ-шка, то такой работник в принципе не нужен. Его можно уволить. Это с одной стороны. А с другой, вот эти вот школы, курсы и т.п., которые сейчас на конвейере создают таких вот специалистов будут так же создавать давление на ЗП.

Но, все это имеет смысл, если все упирается только в количество. Вот вы тоже почему то именно на это акцент сделали.

1
23 ...

Information

Rating
Does not participate
Registered
Activity

Specialization

Frontend Developer, Fullstack Developer
Senior
OOP
TypeScript
JavaScript
React
Vue.js