Боитесь читать исходный код пакета npm? Возьмите вас, чтобы раскрыть философию таро init

внешний интерфейс исходный код

Всего более 9000 слов, и чтение занимает около 10 минут.

напиши первым

Для передней части,githubЭто сокровище. Делайте что угодно, вы должны быть профессионалом, вы можете найти много знаний, особенно во фронтенде, впереди вас ждет много хорошего. Хороший исходный код компонентов, хорошие шаблоны проектирования, хорошие тестовые решения и хорошая структура кода — все это в пределах вашей досягаемости, поэтому не думайте, что вы не можете,coding just api, что вам нужно освоить, так это мышление и мышление программирования.

По сути, эта статья такжеant designПасхальные яйца имеют к этому какое-то отношение. Потому что кто-то сказал, кто сказал тебе не читатьnpmИсходный код пакета, многим может показаться, что чтениеnpmИсходный код пакета — очень сложная штука, но я хочу вам сказать,npmЭто сокровище с передним концом. Ты сможешьnpmПравда, увидев много вещей в сумке, вы можете увидеть лучшее в миреnpmИдеи пакетного программирования.

Например, вы можете увидеть их структуру кода, их зависимости, то, как их код взаимодействует, их спецификации написания кода и так далее. Итак, теперь я передам самую горячую мультитерминальную унифицированную структуру.taroчтобы показать вам, как анализироватьCLIСгенерированоnpmкод пакета. Если статью нельзя проанализировать слишком подробно, я возьму ее в качестве руководства и скажу всем, чтобы она не поддавалась на критику.node_modulesВеревка мешков была напугана, и я не смел смотреть на нее, боясь не понять ее. На самом деле не так непонятно, как вы думаете, вообще известныйnpmПакеты, структуры кода очень дружелюбны, и понять их не сложнее, чем читать код ваших коллег (вы понимаете). и чтениеnpmВ процессе бэггинга вас ждет много сюрпризов и много вдохновения. Ты взволнован, ты счастлив, эм, тогда возьми меня за руку, следуй за мной, и я возьму тебя распутатьnpmУпакуйте эту загадочную и красивую вуаль.

что случилось с таро инит

воплощать в жизньtaro init xxxЗадний,package.jsonЗависимости показаны на рисунке ниже

Вы обнаружите, что при инициализацииCLIКогда установлено много зависимостей, то в это время, если вы перейдете кnode_modules, будет очень неудобно, т.к. установлено много-много зависимых пакетов, что тоже очень много людейnode_modulesПосле каталога причина, по которой он закрывается сразу, он может застрять, если он не закрыт. Тогда давайте поиграем немного спокойнее, не делайте так много, мы входим в режим полос, загружаем пакеты один за другим, следуемtaro initизpackage.jsonустановки, давайте разберем код пакета.

Проанализируйте @tarojs/components

правильноnode_modulesСделайте скриншот, картина выглядит следующим образом:

Из картинки мы видим, что установлено много зависимостей, а пакеты, которые имеют непосредственное отношение к нам,@tarojs,Открытым@tarojsможно увидеть:

На самом деле вы ничего не найдете, давайте посмотримsrcЧто есть в каталоге:

анализироватьsrc/index.jsдокумент

index.jsКод файла следующий:

import 'weui'
export { default as View } from './view'
export { default as Block } from './block'
export { default as Image } from './image'
export { default as Text } from './text'
export { default as Switch } from './switch'
export { default as Button } from './button'
// 其他组件省略不写了

Вы обнаружите, что это концентрацияexportМесто для различных компонентов, из кода здесь мы можем понять, почему вtaroКомпоненты импортируются в следующем виде.

import { View, Text, Icon } from '@tarojs/components'

Например, зачем писать с большой буквы, это потому, что вышеexportТо, что выходит, — в верхнем регистре, при этом все компоненты помещаются в один объект. Подумайте еще раз, зачем писать с большой буквы? В конце концов, это может быть сделано для того, чтобы избежать конфликтов имен с собственными компонентами апплета WeChat.taroзаключается в поддержке родного иtaroСмешанный, если все строчные, как это отличить. Когда вы видите исходный код здесь, вы правыtaroЯвляется ли правило, что введение компонентов должно быть капитализировано, очень естественным? В то же время мы должны испытать большеtaroОтсюда вытекает идея компонента. Чем более частыми, но незаметными будут операции, тем больше мы должны испытать на себе ее прекрасные идеи.

Давайте выберем компонент и посмотрим на структуру, напримерButtonкомпонентов, структура выглядит следующим образом:

Из графика мы можем видетьtaroСтруктура кода основных компонентов , отсюда мы можем получить некоторую информацию:

Первый момент: каждый компонент проходит модульное тестирование с использованиемJest, каталог__test__

Второй момент: каждый компонент имеетindex.md, документация, описывающая компонент

Третий момент: стиль использует только каталогstyleдля хранения, и имя файла записи используется единообразноindex

Четвертый пункт: вtypesв каталогеindex.d.tsнастройки файла, делая подсказки кода более удобными

Резюме после анализа @tarojs/components

с учетомtaroэто новая структура, которая имеет большой потенциал, можем ли мы@tarojs/componentsИзучите некоторые идеи из исходного кода. Например, когда мы разрабатываем собственную библиотеку компонентов, можем ли мы извлечь уроки из этой идеи? На самом деле структура кода этого компонента в настоящее время очень популярна, например, использование самого популярного фреймворка в этом году.JestФреймворк действует как модульный тест для компонентов, используяtsДелайте подсказки по коду. СмотретьgithubЕсли вы используете исходный код на веб-сайте, вы обнаружите, что последнийlernaинструмент для публикации пакетов, используя облегченныйrollupупаковочный инструмент, используя@xxxв видеnamespace. Вот почему я выбираюtaroкадр для анализа причин,taroОн был открыт только в июне 2018 года, поэтому он, должно быть, извлек уроки из новейших передовых технологий и лучших практик без исторического багажа. На самом деле см.taroПосле исходного кода вы найдетеtaroНекоторые концепции дизайна в нем уже превосходят другие известные фреймворки.

Анализ @tarojs/таро

Вы обнаружите, что это все еще установлено в@tarojsкаталог, и никакие другие зависимости не были добавлены.taroСтруктура каталогов показана на следующем рисунке.

Из структуры кода на рисунке мы, вероятно, можем знать:

Первый:typesЕсть каталогindex.d.ts, этот файл являетсяtsфайл, его роль заключается в написании подсказок по коду. Это даст вам очень полезные советы по спецификации кода при написании кода. Напримерindex.d.tsВ нем есть кусок кода (случайный перехват) следующего содержания:

  interface PageConfig {
    navigationBarBackgroundColor?: string,
    backgroundTextStyle?: 'dark' | 'light',
    enablePullDownRefresh?: boolean,
    onReachBottomDistance?: number
    disableScroll?: boolean
  }

Цель этого кода — подсказать вам тип данных этого поля, когда вы пишете соответствующую конфигурацию, давая вам дружественную подсказку. Видя это, на самом деле, мы думаем, что можем добавить такую ​​подсказку и в наш собственный проект, что является хорошей оптимизацией для проекта.

Второе: мы видимdistкаталог, можно сделать вывод, что это выходной каталог, упакованный инструментом упаковки.

Третье: весь каталог очень простой, т.е.taroКакова роль на самом делеtaroявляется средой выполнения.

Давайте взглянемpackage.json,Как показано ниже:

Нашли поле, т.

  "peerDependencies": {
    "nervjs": "^1.2.17"
  }

Обычно мы используем наиболееdependenciesиdevDependencies. ТакpeerDependenciesКакое сознание оно выражает? Заходим в Google Translate, как показано на рисунке:

После распаковки перевода даодноранговая зависимость, в сочетании с переводом, поговорим о роли всего поля, что собственно и означает:

Эта зависимость не обязательно должна находиться в собственном каталоге.npm install. только в корневом каталогеnpm installВот и все. В духе того, чтобы не делать колеса, пожалуйста, ознакомьтесь со следующим для конкретной информации.blog:

Изучите peerDependencies управления зависимостями npm

Давайте взглянемindex.js, всего две строчки кода:

module.exports = require('./dist/index.js').default
module.exports.default = module.exports

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

PS:После написания этой статьи я задумался над этой проблемой и нашел этот способ написания и один из представленных нижеindex.jsТак же пишется:

export {}
export default {}

Сразу понял замысел автора.

анализироватьtaro/src

как показано на рисунке:

Давайте взглянемenv.js

export const ENV_TYPE = {
  WEAPP: 'WEAPP',
  WEB: 'WEB',
  RN: 'RN',
  SWAN: 'SWAN',
  ALIPAY: 'ALIPAY',
  TT: 'TT'
}

export function getEnv () {
  if (typeof wx !== 'undefined' && wx.getSystemInfo) {
    return ENV_TYPE.WEAPP
  }
  if (typeof swan !== 'undefined' && swan.getSystemInfo) {
    return ENV_TYPE.SWAN
  }
  if (typeof my !== 'undefined' && my.getSystemInfo) {
    return ENV_TYPE.ALIPAY
  }
  if (typeof tt !== 'undefined' && tt.getSystemInfo) {
    return ENV_TYPE.TT
  }
  if (typeof global !== 'undefined' && global.__fbGenNativeModule) {
    return ENV_TYPE.RN
  }
  if (typeof window !== 'undefined') {
    return ENV_TYPE.WEB
  }
  return 'Unknown environment'
}

Из приведенного выше кода мы видим, чтоgetEnvфункция для получения среды выполнения нашего текущего проекта, напримерweappвсе ещеswanвсе ещеttи Т. Д. На самом деле, в это время мы должны чувствовать многоконечное единое мышление,genEnvсделал очень важное дело:

использоватьtaroПосле того, как фреймворк напишет код, как конвертировать в мультитерминал? По сути, это переключение на соответствующую среду компиляции согласно среде во время выполнения, тем самым преобразуя ее в код указанного конца. этоgetEnvФункция может визуализировать этот процесс преобразования.

Продолжим смотреть наindex.js, код показан ниже:

import Component from './component'
import { get as internal_safe_get } from './internal/safe-get'
import { set as internal_safe_set } from './internal/safe-set'
import { inlineStyle as internal_inline_style } from './internal/inline-style'
import { getOriginal as internal_get_original } from './internal/get-original'
import { getEnv, ENV_TYPE } from './env'
import Events from './events'
import render from './render'
import { noPromiseApis, onAndSyncApis, otherApis, initPxTransform } from './native-apis'
const eventCenter = new Events()
export {
  Component, Events, eventCenter, getEnv, ENV_TYPE, render, internal_safe_get, internal_safe_set, internal_inline_style, internal_get_original, noPromiseApis, onAndSyncApis,
  otherApis, initPxTransform
}

export default {
  Component, Events, eventCenter, getEnv, ENV_TYPE, render, internal_safe_get, internal_safe_set, internal_inline_style, internal_get_original, noPromiseApis, onAndSyncApis,
  otherApis, initPxTransform
}

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

Нон-стоп, давайте взглянем еще на два важных файла с небольшим количеством кода, один из нихrender.js, другойcomponent.js. код показывает, как показано ниже:

render.js :

export default function render () {}

component.js :

class Component {
  constructor (props) {
    this.state = {}
    this.props = props || {}
  }
}
export default Component

Объем кода очень маленький, пустойrenderфункция, функция с несколькими функциямиComponetКласс, подумайте об этом и знайте, что он делает.

Анализировать глобальный механизм сообщений таро event.js

Давайте взглянемevents.js, псевдокод (сокращенно) выглядит следующим образом:

class Events {
  constructor() {
    // ...
  }
  on() {}
  once() {}
  off() {}
  trigger() {}
}

export default Events

Вы найдете этот файл полнымtaroглобальный механизм уведомления о сообщениях. Это имеютon, once, off, triggerметод,events.jsИмеются соответствующие полные реализации кода. Соответствующие официальные документы:

Механизм сообщения Таро

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

анализироватьinternalсодержание

Продолжим анализ, также необходимо обратить внимание наinternalКаталог, в этом каталоге есть введение, см.internalв каталогеREADME.mdВы можете знать: он экспортируется вinternal_Функция, названная в начале, внутренний метод, о котором пользователю не нужно заботиться и который он не будет использовать, будет автоматически предоставляться каждому использованию во время компиляции.taro-cliСкомпилированный файл плюс его зависимости и используемый. Например:

import { Component } from 'taro'
class C extends Component {
  render () {
    const { todo } = this.state
    return (
      <TodoItem
        id={todo[0].list[123].id}
      />
    )
  }
}

будет скомпилирован в:

import { Component, internal_safe_get } from 'taro'
class C extends Component {
  $props = {
    TodoItem() {
      return {
        $name: "TodoItem",
        id: internal_safe_get(this.state, "todo[0].list[123].id"),
      }
    }
  }
  ...
}

Во время компиляции он автоматически предоставляется при каждом использованииtaro-cliСкомпилированный файл плюс его зависимости и используемый. Что означает это предложение? может бытьtaro-cliПри компиляции файл нужно соответствующим образом обработать таким образом. В настоящее время я понимаю это так до поры до времени, и это нормально, что я не могу понять это до поры до времени, и продолжаю анализировать ниже.

анализироватьtarojs/taroрезюме

tarojs/taroАнализ почти завершен. Из анализа мы получили более общее представление о том, как среда выполнения макроскопически соединяет несколько терминалов и как передатьtsфайл, чтобы добавить дружественные подсказки в код. Так как естьinternal, значит неinternalФайлы в каталоге могут предоставлять внешние методы, такие какevents.js, который также может вдохновить нас. Как определить внутренний и внешний код, как разделить.

Проанализируйте несколько сознательных функциональных файлов

Сначала установите зависимости:

yarn add @tarojs/taro-weapp && nervjs && nerv-devtools -S

Затем мы смотрим на последнюю структуру пакета

соответствующийpackage.jsonследующее:

{
  "dependencies": {
    "@tarojs/components": "^1.2.1",
    "@tarojs/router": "^1.2.2",
    "@tarojs/taro": "^1.2.1",
    "@tarojs/taro-weapp": "^1.2.2",
    "nerv-devtools": "^1.3.9",
    "nervjs": "^1.3.9"
  }
}

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

анализироватьomit.js

Давайте взглянем:omit.js

import _extends from "babel-runtime/helpers/extends";
function omit(obj, fields) {
  var shallowCopy = _extends({}, obj);
  for (var i = 0; i < fields.length; i++) {
    var key = fields[i];
    delete shallowCopy[key];
  }
  return shallowCopy;
}

export default omit;

отomit.jsизreadme.mdМы можем знать, что он генерирует объект, который удаляет указанное поле и является поверхностной копией.

анализироватьslash.js

код показывает, как показано ниже:

'use strict';
module.exports = input => {
	const isExtendedLengthPath = /^\\\\\?\\/.test(input);
	const hasNonAscii = /[^\u0000-\u0080]+/.test(input);
	if (isExtendedLengthPath || hasNonAscii) {
		return input;
	}
	return input.replace(/\\/g, '/');
};

отslashизreadme.mdмы можем знать

This was created since the path methods in Node outputs \\ paths on Windows.

Конкретное осознание, проанализируйте сами, это не сложно.

анализироватьvalue-equal.js

value-equalОсновное содержание следующее:

var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };

function valueEqual(a, b) {
  if (a === b) return true;
  if (a == null || b == null) return false;
  if (Array.isArray(a)) {
    return Array.isArray(b) && a.length === b.length && a.every(function (item, index) {
      return valueEqual(item, b[index]);
    });
  }
  var aType = typeof a === 'undefined' ? 'undefined' : _typeof(a);
  var bType = typeof b === 'undefined' ? 'undefined' : _typeof(b);
  if (aType !== bType) return false;
  if (aType === 'object') {
    var aValue = a.valueOf();
    var bValue = b.valueOf();
    if (aValue !== a || bValue !== b) return valueEqual(aValue, bValue);
    var aKeys = Object.keys(a);
    var bKeys = Object.keys(b);
    if (aKeys.length !== bKeys.length) return false;
    return aKeys.every(function (key) {
      return valueEqual(a[key], b[key]);
    });
  }
  return false;
}
export default valueEqual;

отvalue-equalизreadme.mdМы можем знать, что этот метод: только сравнитьkeyсоответствующийvalueценность. Тщательно прочувствуйте идею написания такого кода.

анализироватьprop-types.js

Давайте взглянемprop-types, исходный код здесь не указан. СмотретьREADME.md,мы знаем

Runtime type checking for React props and similar objects.

этоreactв рамкеpropsВспомогательный инструмент для проверки типов, то есть для выполнения следующей функции

XxxComponent.propTypes = {
  xxProps: PropTypes.xxx
}

Анализировать js-токены

Давайте взглянемjs-tokens, код показан ниже:

Object.defineProperty(exports, "__esModule", {
  value: true
})
exports.default = /((['"])(?:(?!\2|\\).|\\(?:\r\n|[\s\S]))*(\2)?|`(?:[^`\\$]|\\[\s\S]|\$(?!\{)|\$\{(?:[^{}]|\{[^}]*\}?)*\}?)*(`)?)|(\/\/.*)|(\/\*(?:[^*]|\*(?!\/))*(\*\/)?)|(\/(?!\*)(?:\[(?:(?![\]\\]).|\\.)*\]|(?![\/\]\\]).|\\.)+\/(?:(?!\s*(?:\b|[\u0080-\uFFFF$\\'"~({]|[+\-!](?!=)|\.?\d))|[gmiyus]{1,6}\b(?![\u0080-\uFFFF$\\]|\s*(?:[+\-*%&|^<>!=?({]|\/(?![\/*])))))|(0[xX][\da-fA-F]+|0[oO][0-7]+|0[bB][01]+|(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?)|((?!\d)(?:(?!\s)[$\w\u0080-\uFFFF]|\\u[\da-fA-F]{4}|\\u\{[\da-fA-F]+\})+)|(--|\+\+|&&|\|\||=>|\.{3}|(?:[+\-\/%&|^]|\*{1,2}|<{1,2}|>{1,3}|!=?|={1,2})=?|[?~.,:;[\](){}])|(\s+)|(^$|[\s\S])/g

комбинироватьREADME.md, мы обнаружим, что он использует обычныйJSграмматика становится одна за другойtoken , so cool.

exampleследующее:

var jsTokens = require("js-tokens").default
var jsString = "var foo=opts.foo;\n..."
jsString.match(jsTokens)
// ["var", " ", "foo", "=", "opts", ".", "foo", ";", "\n", ...]

Можете ли вы написать такое антинебесное правило?

Резюме различных небольших функций

Считаете ли вы, что эти функциональные файлы вполне сознательны? Если вы хотите увидеть, как они реализованы, вы можете продолжить просмотр исходного кода. Вы обнаружите, что многие вещи реализованы конкретно, и вам не нужно их запоминать. вообще. Давайте посмотрим на вышеjs-token, value-equal, prop-types omit, slashи т. д. На самом деле, все они очень хорошие функции. Они могут вдохновить нас на программирование. Мы можем извлечь уроки из идей и методов реализации этих функций, чтобы лучше улучшить нашу работу.JSспособность к программированию, которая есть и в чтенииnpmОчень важный урожай в процессе пакета исходного кода.

Анализ @tarojs/taro-weapp

Эта сумка используется дляtaroНаписанный код компилируется в код апплета WeChat, и структура кода показана на рисунке:

Первый изreadme.md, мы не можем видеть, что делает этот пакет, только одно предложение, базовая структура апплета многотерминального решения. Так что я думаю, что это,taroКоманде еще предстоит дополнить его соответствующим образом. здесьreadme.mdСлишком лаконично написано.

Но мы можем проанализировать это, прочитав кодtaro-weappЧто он делает?Во-первых, давайте посмотрим на структуру кода. имеютdist,srcподожди, иnode_modules. В это время, после того, как мы подумали о пакете, представленном выше, мы задались таким вопросом, почемуnode_modulesсодержание. Какова его цель? не могу использовать вышеуказанноеpeerDependenciesреши? В связи с этим я пока не могу понять эту вещь, что мне делать, когда я сталкиваюсь с такой проблемой? В это время мы можем перестать глубоко думать об этой проблеме, чтобы не заблокировать, и продолжить анализировать другие коды.

Смотрим как обычноreadme.md,ноreadme.mdИнформация в одном предложении, основная структура апплета многотерминального решения. Что же делать, не отчаивайтесь! Восемь лет войны, продолжаем анализировать.

Давайте взглянемpackage.json, часть кода выглядит следующим образом:

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "rollup -c rollup.config.js",
    "watch": "rollup -c rollup.config.js -w"
  },
  "dependencies": {
    "@tarojs/taro": "1.2.2",
    "@tarojs/utils": "1.2.2",
    "lodash": "^4.17.10",
    "prop-types": "^15.6.1"
  }

отpackage.jsonМы можем найти две основные вещи, первая - это зависимости, необходимые для этого пакета, вы можете увидеть зависимости@tarojs/taro, @tarojs/utils, lodash, prop-types. Затем мы смотрим наnode_modules, обнаружил, что только@tarojs/taro. Другие устанавливаются снаружи, такие какlodash, prop-typesВы можете использовать пакеты в корневом каталоге, здесь@tarojs/utilsнедавно установлен. существуетtaroПод содержанием. Имея эту информацию на руках, мы объединим вышеуказанное понимание и подумаем над несколькими вопросами:

  1. почему это не работаетpeerDependencies
  2. зачем ставить@tarojs/taroустановлен наtaro-weappвнутри сумки.
  3. Почемуtaro-weappнетtypes/index.d.tsтакой файл

проблемаmarkНа мгновение сначала отбросьте проблему, а потом займитесь глубокими размышлениями. Помните одну вещь, нам не нужно достигать уровня полного понимания при чтении исходного кода, и не обязательно быть реалистом. Все, что нам нужно сделать, это бросить вопрос и перейти к анализу, теперь мы читаемindex.js, код показан ниже:

module.exports = require('./dist/index.js').default
module.exports.default = module.exports

Это понятноdistКаталог представляет собой упакованный каталог, теперь давайте его проанализируемsrcсодержание,srcсерединаindexКод файла следующий:

/* eslint-disable camelcase */
import {
  getEnv, Events, eventCenter, ENV_TYPE, render,
  internal_safe_get, internal_safe_set,
  internal_inline_style, internal_get_original
} from '@tarojs/taro'

import Component from './component'
import PureComponent from './pure-component'
import createApp from './create-app'
import createComponent from './create-component'
import initNativeApi from './native-api'
import { getElementById } from './util'

export const Taro = {
  Component, PureComponent, createApp, initNativeApi,
  Events, eventCenter, getEnv, render, ENV_TYPE,
  internal_safe_get, internal_safe_set,
  internal_inline_style, createComponent,
  internal_get_original, getElementById
}
export default Taro
initNativeApi(Taro)

отindex.js, мы видим, что импорт@tarojs/taroнекоторые методы. В статье уже проанализировано@tarojs/taro. Теперь давайте подумаем об этом в сочетании, мы можем найти это: используйте@tarojs/taro-weappбуду использоватьtaroКогда написанный код компилируется в апплет WeChat, необходимо использовать@tarojs/taroпакет для реализации преобразования вместе.

примерно знаюtaro-weappэффект. Теперь давайте проанализируемindex.jsВнешние файлы, от которых зависит анализ, следующие:

Анализ src/components.js

Сделайте отступ в коде, давайте посмотрим на общий код, как показано на рисунке:

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

import { enqueueRender } from './render-queue'
import { updateComponent } from './lifecycle'
import { isFunction } from './util'
import {
  internal_safe_get as safeGet
} from '@tarojs/taro'
import { cacheDataSet, cacheDataGet } from './data-cache'
const PRELOAD_DATA_KEY = 'preload'
class BaseComponent {
  // _createData的时候生成,小程序中通过data.__createData访问
  __computed = {}
  // this.props,小程序中通过data.__props访问
  __props = {}
  __isReady = false
  // 会在componentDidMount后置为true
  __mounted = false
  // 删减了一点
  $componentType = ''
  $router = {
    params: {},
    path: ''
  }
  constructor (props = {}, isPage) {
    this.state = {}
    this.props = props
    this.$componentType = isPage ? 'PAGE' : 'COMPONENT'
  }
  _constructor (props) {
    this.props = props || {}
  }
  _init (scope) {
    this.$scope = scope
  }
  setState (state, callback) {
    enqueueRender(this)
  }
  getState () {
    const { _pendingStates, state, props } = this
    const queue = _pendingStates.concat()
    queue.forEach((nextState) => {
      if (isFunction(nextState)) nextState = nextState.call(this, stateClone, props)
      Object.assign(stateClone, nextState)
    })
    return stateClone
  }
  forceUpdate (callback) {
    updateComponent(this)
  }
  $preload (key, value) { // 省略 }
  // 会被匿名函数调用
  __triggerPropsFn (key, args) {}
}
export default BaseComponent

Давайте взглянем на приведенный выше код.Из названия мы знаем, что это базовый класс компонента, который можно понять, так как все компоненты должны наследоватьBaseComponent. Разберем приведенный выше код, сначала разберем первый пункт, почему так много подчеркивающих переменных? На самом деле эти переменные предназначены для собственного использования, давайте посмотрим на следующий код:

class BaseComponent {
  // _createData的时候生成,小程序中通过data.__createData访问
  __computed = {}
  // this.props,小程序中通过data.__props访问
  __props = {}
  __isReady = false
  // 会在componentDidMount后置为true
  __mounted = false
  // 删减了一点
  $componentType = ''
  $router = { params: {}, path: ''}
}

Сначала я помнюES6Не поддерживается запись переменных непосредственно в классе, это нужно делать черезbabelподдержать его, как написано. По комментариям в коде вы в основном знаете функцию этой переменной, например можно передатьdata.__propsдоступ к__props. этоthis.propsЗдесь также используется значение режима прокси. какvueметод доступа в . Хорошо, мы это поняли, тогда продолжим смотреть на следующий код:

class BaseComponent {
  constructor (props = {}, isPage) {
    this.state = {}
    this.props = props
    this.$componentType = isPage ? 'PAGE' : 'COMPONENT'
  }
  _constructor (props) {
    this.props = props || {}
  }
}

Смотри, что мы нашли, "конструкторов" два, хахаха, я тебе наврал, конструктор всего один, т.е.constructor. Но следующее_constructorКакая, черт возьми, функция, она все еще происходит внутриthis.props = props || {}Операция, что это за хрень, если вы читалиtaroВ официальной документации вы можете увидеть такую ​​подсказку:

даже если ты не пишешьthis.props = props, это нормально, потому чтоtaroВо время работы необходимо использоватьpropsсделай что-нибудь.

Но вы можете не понимать почему, вы всегда чувствуете, что текстовое описание не настоящее из кода, поэтому, когда вы видите приведенный выше код, вы испытываете настоящее чувство, потому что вы видите код. ФактическиtaroИспользуйте свой собственный внутренний метод_constructorдавайthis.props = props || {}работать. Так документ подскажет, что: не писатьpropsТакже может.

другие, такие какsetState,getStateПодождите, пока сами не проанализируете, путь тот же. В любом случае, пока вы анализируете это, вы можете получить более глубокое понимание этого. Возможно, в этот момент вы забыли о том, что написано в официальной документации сайта, но значение этой строчки в коде вы не забудете.

Проанализируйте src/native-api.js

Код этого файла очень важен, почему он называетсяnative-api. Если вы посмотрите официальную документацию, вы увидите эту страницу:

На самом деле здесьnative-api.jsЭто введение вышеприведенной картины, которую можно понять какTaroРодной для мини-программ WeChatapiинкапсуляция выполнена.

Давайте взглянемnative-api.jsКаков результат, код выглядит следующим образом

export default function initNativeApi (taro) {
  processApis(taro)
  taro.request = request
  taro.getCurrentPages = getCurrentPages
  taro.getApp = getApp
  taro.requirePlugin = requirePlugin
  taro.initPxTransform = initPxTransform.bind(taro)
  taro.pxTransform = pxTransform.bind(taro)
  taro.canIUseWebp = canIUseWebp
}

Здесь, чтобы экспортироватьinitNativeApiметод. Увидев приведенный выше код, вы знаете общую картину всего входа? Этот экспортированный метод выполняется в записи, чтобыtaroдополнен. Давайте начнем сtaro-weappВ файле ввода проверьте, нет ли выполненияinitNativeApi(Taro)изTaroЧто такое объект, код выглядит следующим образом:

const Taro = {
  Component, PureComponent, createApp, initNativeApi, Events,
  eventCenter, getEnv, render, ENV_TYPE, internal_safe_get,
  internal_safe_set, internal_inline_style,
  createComponent, internal_get_original, getElementById
}

Как видно из приведенного выше кода,Taroкак будтоkoaсерединаctx, многие методы монтируются в виде контекста привязки. Но здесь оптимизация производится путем передачиinitNativeApi(Taro)способ датьTaroСмонтируйте больше методов. Посмотрим на исполнениеinitNativeApi(Taro)ПослеTaroЧто такое объект, код выглядит следующим образом:

const Taro = {
  // 上面的导出依然存在,这里不重复写了
  request,
  getCurrentPages,
  getApp,
  requirePlugin,
  initPxTransform,
  pxTransform,
  canIUseWebp,
}

processApis(taro)Не говоря уже об этом.

Мы смотрим на приведенный выше код и обнаруживаем, что есть еще много методов, мы можем понять это, выполнивinitNativeApi(Taro), так чтоTaroУстановлен какой-то локальный апплет WeChatAPI. Но вы обнаружите, что некоторые из них не местныеAPI, но это можно понять и таким образом, напримерrequest, getCurrentPages, getApp. Я лично понимаю, что причина, по которой автор делает это, заключается в развязке,nativeи неnativeметод отдельно.

Анализ src/pure-component.js

import { shallowEqual } from '@tarojs/utils'
import Component from './component'
class PureComponent extends Component {
  isPureComponent = true
  shouldComponentUpdate (nextProps, nextState) {
    return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState)
  }
}
export default PureComponent

Давайте взглянемpure-componnet.jsкод. Вам было очень легко это понять?PureComponentкласс наследуетComponent. В то же время он осозналshouldComponentUpdateметод. И этот код метода выглядит следующим образом:

shouldComponentUpdate (nextProps, nextState) {
    return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState)
}

Вы обнаружите, что его записьnextProps , nextState. затем пройтиshallowEqualМетоды иprops, stateсравнивать, покаshallowEqualСлушая название, вы понимаете, что это поверхностное сравнение. Конкретный код находится в@taro/utilв каталогеsrcв каталогеshallow-equal.js, код выглядит следующим образом:

Object.is = Object.is || function (x, y) {
  if (x === y) return x !== 0 || 1 / x === 1 / y
  return x !== x && y !== y
}

export default function shallowEqual (obj1, obj2) {
  if (obj1 === null && obj2 === null) return true
  if (obj1 === null || obj2 === null) return false
  if (Object.is(obj1, obj2)) return true
  const obj1Keys = obj1 ? Object.keys(obj1) : []
  const obj2Keys = obj2 ? Object.keys(obj2) : []
  if (obj1Keys.length !== obj2Keys.length) return false

  for (let i = 0; i < obj1Keys.length; i++) {
    const obj1KeyItem = obj1Keys[i]
    if (!obj2.hasOwnProperty(obj1KeyItem) || !Object.is(obj1[obj1KeyItem], obj2[obj1KeyItem])) {
      return false
    }
  }
  return true
}

Глядя на код, я обнаружил, что это поверхностное сравнение. Видишь это, ты чувствуешьPureComponentЭто не так абстрактно и сложно для понимания, как кажется.ReactсерединаPureComponentЭто тоже причина. Так что нет необходимости запоминать какие-то жизненные циклы фреймворков и различные профессиональные названия. На самом деле, когда вы снимете с нее завесу и увидите ее истину, вы обнаружите, что ее структура не так глубока. Но если у вас просто не хватает мужества поднять завесу и взглянуть ей в лицо, тогда вы будете все время воображать это, не подозревая об истине.

Анализ src/create-componnet.js

Давайте взглянем

  const weappComponentConf = {
    data: initData,
    created (options = {}) {
      this.$component = cacheDataGet(preloadInitedComponent, true)
      this.$component = new ComponentClass({}, isPage)
      this.$component._init(this)
      this.$component.render = this.$component._createData
      this.$component.__propTypes = ComponentClass.propTypes
      Object.assign(this.$component.$router.params, options)
    },
    attached () {},
    ready () {
      componentTrigger(this.$component, 'componentDidMount')
    },
    detached () {
      componentTrigger(this.$component, 'componentWillUnmount')
    }
  }

Из приведенного выше кода мы видим, что это будет использоватьсяtaroНаписанные компоненты компилируются в собственные экземпляры компонентов в программе апплета WeChat. Один момент, на который следует обратить внимание, заключается в том, чтоattachedиспользуется в методеcacheDataGetиcacheDataHas, введены два вышеупомянутых метода, почему они здесь используются, какова цель и какой смысл за ними стоит? Нам необходимо рассмотреть и проанализировать значение жизненного цикла компонента апплета WeChat. В то же время мы должны думать об этом предложении в компонентеthis.$component.render = this.$component._createDataСмысл кода, поймите его хорошоcreatedЧто именно произошло.

Проанализируйте src/create-app.js

function createApp (AppClass) {
  const app = new AppClass()
  const weappAppConf = {
    onLaunch (options) {
      app.$app = this
      app.$app.$router = app.$router = {
        params: options
      }
      if (app.componentWillMount) app.componentWillMount()
      if (app.componentDidMount) app.componentDidMount()
    },
    onShow (options) {},
    onHide () {},
    onError (err) {},
  }
  return Object.assign(weappAppConf, app)
}
export default createApp

На первый взгляд, мы знаем, что это конфигурация уровня апплета, используемая для создания апплета WeChat.ifзаявление, вы можете почувствовать цель за ним. посмотри сноваObject.assign(weappAppConf, app)знаешь,taroкак следоватьreactИдея программирования неизменяемых данных.

Проанализируйте src/next-tick.js

const nextTick = (fn, ...args) => {
  fn = typeof fn === 'function' ? fn.bind(null, ...args) : fn
  const timerFunc = wx.nextTick ? wx.nextTick : setTimeout
  timerFunc(fn)
}
export default nextTick

Этот код также легко понять, поместив код вwx.nextTickилиsetTimeoutдля достижения выполнения на следующем этапе цикла.

Проанализируйте src/render-queue.js

import nextTick from './next-tick'
import { updateComponent } from './lifecycle'
let items = []
export function enqueueRender (component) {
  if (!component._dirty && (component._dirty = true) && items.push(component) === 1) {
    nextTick(rerender)
  }
}
export function rerender () {
  let p
  const list = items
  items = []
  while ((p = list.pop())) {
    if (p._dirty) {
      updateComponent(p, true)
    }
  }
}

Знайте это, назвав егоnextTickРендеринг идеи.

Анализ src/lifecycle.js

Мы сжимаем функцию и обнаруживаем, что только экспортupdateComponentметод, из названия мы знаем, что это обновление осведомленности о компоненте.

Проанализируйте src/data-cache.js

const data = {}
export function cacheDataSet (key, val) {
  data[key] = val
}
export function cacheDataGet (key, delelteAfterGet) {
  const temp = data[key]
  delelteAfterGet && delete data[key]
  return temp
}
export function cacheDataHas (key) {
  return key in data
}

Мы можем знать из кода, что это для кэширования данных. Сначала кэшируйте, а потом каждый раз извлекайтеvalue, положи этоvalueУдалить. Так почему же он спроектирован именно так, каковы причины и каковы преимущества такого дизайна? Вы можете подумать об этом позже, это также хорошая идея для программирования.

анализировать@tarojs/taro-weappпострезюме

через пару@tarojs/taro-weappАнализ мы знаем конкретно: при беге,taroчерезgetEnvсократить код доtaro-weappсреда для компиляции. Затем мы проанализировали,taro-weappКак компилировать и обрабатывать, например, как решить мультитерминалAPIразные вопросы. Благодаря анализу мы получаем более глубокое пониманиеtaroВся архитектурная идея и часть внутренней реализации. Эти идеи стоит попрактиковать в наших обычных проектах. На самом деле, какова цель исходного кода, такого как мой анализtaro initИз анализа до сих пор, если вы прочитаете его, вы обнаружите, что есть много классных идей.Может быть, в вашем мире вы можете использовать это таким образом, даже если вы написали несколько проектов и не можете их вспомнить. Цель просмотра исходного кода — позволить вам прикоснуться к нему. Как разрабатываются лучшие в мире проекты с открытым исходным кодом. Чтобы впитать эти мысли, использовать их для себя и заставить меня расти.

анализироватьrollup-plugin-alias

отreadme.md, мы можем обнаружить, что он делает одну вещь, то есть абстрагирует путь введения пакета, который имеет много преимуществ, так что вам не нужно об этом заботиться.../Этот вид символа может выполнять централизованную модификацию. В чем наше вдохновение, на самом деле, мы можем узнать изrollup-plugin-aliasНаучитесь управлять своимиnpmСумка. Мы должны усвоить этот тип мышления.

анализироватьresolve-pathname

Что оно делает? В сочетании с исходным кодом изreadme.md, мы можем обнаружить, что на самом деле он делает то же самое, то есть предоставляет нам метод для работы сURL, или маршрут, с помощью этого метода мы можем что-то сделать с заданным маршрутом, например, вернуть новый маршрут.

оinvariant、warningВсе они являются вспомогательными инструментами для обработки подсказок, так что не будем об этом, читайте исходники для анализа сами.

Анализ @tarojs/маршрутизатор

Скриншот структуры каталогов кода выглядит следующим образом:

мы увидим вrouterкаталог, естьdistиtypesсодержание. ноsrcкаталог, но почему некоторые пакеты имеютsrcНу есть такие? Это проблема, требующая дальнейшего детального анализа.

Как узнать больше интересного

какnode_modulesНайдите что-нибудь поинтереснее. Позвольте мне привести пример, например, давайте посмотрим наbindРеализация в разных пакетах: На картинке нижеcore-jsсерединаmodulesв каталогеbindвыполнить

код показывает, как показано ниже:

var aFunction = require('./_a-function');
var isObject = require('./_is-object');
var invoke = require('./_invoke');
var arraySlice = [].slice;
var factories = {};

var construct = function (F, len, args) {
  if (!(len in factories)) {
    for (var n = [], i = 0; i < len; i++) n[i] = 'a[' + i + ']';
    factories[len] = Function('F,a', 'return new F(' + n.join(',') + ')');
  } return factories[len](F, args);
};

module.exports = Function.bind || function bind(that /* , ...args */) {
  var fn = aFunction(this);
  var partArgs = arraySlice.call(arguments, 1);
  var bound = function (/* args... */) {
    var args = partArgs.concat(arraySlice.call(arguments));
    return this instanceof bound ? construct(fn, args.length, args) : invoke(fn, args, that);
  };
  if (isObject(fn.prototype)) bound.prototype = fn.prototype;
  return bound;
};

Давайте посмотрим на это сноваlodashсерединаbindРеализация, код выглядит следующим образом:

var baseRest = require('./_baseRest'),
    createWrap = require('./_createWrap'),
    getHolder = require('./_getHolder'),
    replaceHolders = require('./_replaceHolders');
    
var WRAP_BIND_FLAG = 1,
    WRAP_PARTIAL_FLAG = 32;

var bind = baseRest(function(func, thisArg, partials) {
  var bitmask = WRAP_BIND_FLAG;
  if (partials.length) {
    var holders = replaceHolders(partials, getHolder(bind));
    bitmask |= WRAP_PARTIAL_FLAG;
  }
  return createWrap(func, bitmask, thisArg, partials, holders);
});
bind.placeholder = {};
module.exports = bind;

Сравнивая два кода, мы можем обнаружить, что формы реализации двух кодов различны. Пожалуй, то, что обычно понятно всем, — это первый способ написания, и почти все статьи написаны первым способом, который легко понять. Но второй метод письма более сложен для понимания.По сравнению с первым методом письма, второй метод письма более абстрактный и несвязанный. Например, он более функционален.На самом деле, если вы владеете функциональным программированием,bindПо сути это реализация частичной функции Второй способ написания уже отражен в именовании.partials. Например, в интервью, если спроситьbindКак это реализовать, можете ли вы написать два метода реализации (идеи программирования)? Может быть, интервьюер не понимает его после того, как вы его написали. Вот пример, их гораздо больше, изучите сами. (Кстатиcore-jsиlodashвводится пакет. . )

Понимание события пасхального яйца в дизайне муравья

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

Если есть проблема, как решить, так и решить, а силу еще надо рвать, кто бы ни брал на себя вину.

История выглядит так:

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

иant design, как и у попавшего в беду коллеги выше, коснулось всех, ноantОн также проявил инициативу, чтобы признать свою ошибку, и горшок также проявил инициативу, чтобы запомнить ее, и сразу дал план.

На самом деле, я лично считаю, что тем, кто потерял работу из-за этого дела, некомфортнее. Но для тех, кто говорит яростно (неприятно), то есть, рот неприятен, и есть несколько обид, которые в значительной степени поднимутся из-за внешнего интерфейса.Цель неприятности - не что иное, как неявный шпор .antкоманда. я думаюantЯ также понял, что точно больше не буду заниматься подобными вещами в будущем.

Я все еще надеюсь, что вы:

Поскольку мы решили верить с самого началаant design, тогда у нас больше терпимости, на этот раз терпимостиant designделать ошибки, не отрицайте их все из-за одной ошибки.

На самом деле вы находитесь в одной компании. Вы совершили ошибку, от которой пострадали многие коллеги. Вы осознали серьезность вопроса. Вам было очень грустно и горько. Вы обнаружили, что делаете крайне глупый поступок. Я очень хочу помириться. за это, но время не может вернуться, время не может вернуться, все, что вы можете сделать, это убедиться, что вы не сделаете ошибок снова в следующий раз, и вы действительно хотите всеобщего прощения и доверия. Хотя ты искренне признаешь свою ошибку, я надеюсь, что все могут доверять тебе по-прежнему, но если все делают ошибку из-за тебя, они не так доверяют тебе в своем поведении и речи. Тогда, в это время, ваше сердце должно быть чрезвычайно потерянным и холодным.

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

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

Ладно, не будем нести чушь о моем личном мнении.

Примечание

О статье немного длинно

Так как статья действительно длинновата, то я провел некоторые манипуляции с выложенным кодом, например удалил часть кода и записал его в три строки.ifзаявление, написанное одной строкой. Пучокimport, exportПишите вещи вместе как можно больше, не обертывая. Так что если вы хотите увидеть статью без сокращенной версии, вы можете перейти на мойgithubПогляди,githubПодключение: https://github.com/godkun/blog/issues/30

Что мне делать, если я столкнусь с чем-то, чего не понимаю при чтении пакета npm

заnpmИсходный код пакета, когда я его смотрел, тоже не понял некоторых мест, что для нас нормально (NBКроме босса) но я не буду блокировать свое понимание всего пакета, потому что я не могу понять определенный абзац или определенный файл.Я добавлю свое понимание, даже если оно неверно, но пока я могу бегло поставить весь пакет Достаточно понять пакет так, как я хочу. Не пытайтесь полностью понять, если вы иnpmОб этом сообщил автор пакета.

Вы обнаружите, что в этой статье в процессе анализа уже есть некоторые вопросы, и у меня нет однозначного ответа, как и на загруженныхLOLОбучающие видео, пока они загружены, представляют собой всевозможные классические ходы, предсказания и кокетливые операции. Но на самом деле коленей могло быть больше 10. Говоря об этом, я вдруг подумал о посте на Zhihu.В нем вроде бы спрашивали, что программа обычно пишет код.Также выкладывали картинку Матрицы,спрашивая,так ли это на самом деле? Потом был один, кто ответил видео, и я чуть не рассмеялся, увидев его. На самом деле, выведите это, вы будете знатьnpmКогда исходный код упакован, плавное плавание невозможно. Должно быть что-то, чего ты не понимаешь, иnpmисточник пакета иgithubна соответствующемnpmИсходный код пакета отличается.npmсумка похожаgithubВверхnpmИсходный код проходит через инструмент управления пакетами,buildпосле выхода. вам никогда не придетсяdistКаталог можно увидеть, напримерgithubсерединаtaroиспользуется в исходном кодеrollupУпакованы в небольшие пакеты.

Это нормально — столкнуться с чем-то, чего вы не понимаете, вам нужно понять целое и игнорировать части.

Вывод в конце статьи

Прочитав это, вы обнаружите, что я не ставилtaro initВсе зависимости загрузки анализируются один раз, потому что если анализ закончить, то может родиться короткий рассказ, а это бессмысленно. Я всего лишь играю роль привлечения других, надеюсь, вы что-то приобретете после прочтения моей статьи, не бойтесь.npmСумка,npmПакеты тоже пишутся людьми.

При анализе рекомендую качать один пакет один за другим, а потом качать пакет и смотреть каталог. Это поможет вам понять, что многие людиnpm iилиyarn installвыключи его, а потом включиnode_modulesкаталог, а потом я был ошеломлен, и я не знал, какой пакет искать. Итак, когда вы хотите узнать о чем-то, лучший способ — загружать по одному пакету за раз и просматривать его по частям, чтобы увидеть изменения в структуре кода до и после, а также изменения в пакете. Потом вы обнаружите, что количество упаковок медленно увеличивается, но вы совсем не паникуете, потому что уже примерно знаете их функции и содержимое.

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

передняя частьgithubОдна из самых выгодных отраслей в мире, из-за самой передовой технологии с открытым исходным кодом, исходный код находится вgithubначальство,githubЭто сокровище переднего конца, неиссякаемое и неиссякаемое.react,vue,angular,webpack,babel,node,rxjs,three.js,TypeScript,taro,ant-design,egg,jest,koa,lodash,parcel,rollup,d3,redux,flutter,cax,lerna,hapi,jsx,eslintПодождите, бла-бла, сокровища там, вы хотели бы открыть их и увидеть правду?

Ссылка на ссылку

захватывающий момент

Статьи серии Nuggets можно найти в моемgithubНайдено на, добро пожаловать, чтобы обсудить, отправить адрес:

https://github.com/godkun/blog

Я думаю, это хорошо, вы можете заказать егоstarи, как, поощрение и поощрение.

Первый раз, когда я раскрыл свой самый загадочный аккаунт на сайте знакомств (дайверский побег)

За кулисами

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

Велика вероятность, что в этой статье будут ошибки, но велика вероятность, что будут и очень хорошие места.

так............

С Новым годом!