Třídy JavaScriptu – přátelský úvod Pt.2

Třídy JavaScriptu mohou učinit váš kód čistším a čitelnějším. Tento článek vám pomůže porozumět konceptům, jako jsou pole tříd, přístupové objekty getter a setter a mixiny. Naučte se pracovat s třídami JavaScriptu jako profík, zdokonalte své programovací dovednosti a staňte se lepším vývojářem JavaScriptu.

Třídy JavaScriptu – přátelský úvod, část 1.

JavaScriptové třídy a pole tříd

Čím častěji začnete třídy JavaScript používat, tím rychleji si na novou syntaxi zvyknete. Jak již bylo řečeno, budoucí návrhy ECMAScript mohou usnadnit práci s třídami JavaScript. Pole třídy jsou jedním příkladem. Pamatujete si třídu constructor ? Dobrou zprávou je, že již nemusí být vyžadován.

Cílem polí tříd, nazývaných také vlastnosti tříd, je umožnit vývojářům JavaScriptu vytvářet jednodušší konstruktory v třídách JavaScriptu. Jednoduše řečeno, již nebudete muset deklarovat vlastnosti třídy uvnitř constructor . Místo toho je můžete deklarovat přímo, mimo něj a dokonce vynechat constructor sám.

Veřejná pole a metody

Jedna věc, kterou musíte vědět a pamatovat si ji do budoucna, je, že když to uděláte, všechna pole, která jste deklarovali, se ve výchozím nastavení stanou „veřejnými“. To znamená, že všechna tato pole budou přístupná zevnitř i zvenčí třídy. Budete je moci zkoumat a měnit, jak budete chtít. Totéž platí pro metody.

// ES6 class - without class fields
class SoccerPlayer {
  // Declare all class properties inside the constructor
  constructor() {
    this.assists = 0
    this.goals = 0
    this.number = null
    this.position = null
    this.team = null
  }

  addAssist() {
    this.assists++
  }

  addGoal() {
    this.goals++
  }

  addToTeam(team) {
    this.team = team
  }

  assignNumber(number) {
    this.number = number
  }

  assignPosition(position) {
    this.position = position
  }
}

// ESNext class - with public class fields
class SoccerPlayer {
  // Declare all properties directly, as public by default
  assists = 0
  goals = 0
  number = null
  position = null
  team = null

  // All these methods created as public by default
  addAssist() {
    this.assists++
  }

  addGoal() {
    this.goals++
  }

  addToTeam(team) {
    this.team = team
  }

  assignNumber(number) {
    this.number = number
  }

  assignPosition(position) {
    this.position = position
  }
}

Jak můžete vidět, pole tříd mohou usnadnit práci s třídami JavaScript a váš kód čistší a čitelnější. To platí zejména, pokud pracujete s Reactem. Pole tříd vám pak mohou pomoci snížit kód JavaScript ještě více.

// ES6 class + React - without class fields
import * as React from 'react'

class MyComponent extends React.Component {
  // Declare class state inside the constructor
  constructor(props) {
    super(props)

    this.state = {
      firstName: '',
      lastName: '',
      age: 0
    }

    this.handleInputChange = this.handleInputChange.bind(this)
  }

  handleInputChange(event) {
    this.setState({
      [event.target.name]: [event.target.value]
    })
  }

  render() { ... }
}


// ESNext class + React - with class fields
import * as React from 'react'

class MyComponent extends React.Component {
  // Declare class state directly as public class property
  state = {
    firstName: '',
    lastName: '',
    age: 0
  }

  handleInputChange = (event) => {
    this.setState({
      [event.target.name]: [event.target.value]
    })
  }

  render() { ... }
}

Návrh oborů třídy je v současné době ve fázi 3 TC39. Pokud vše půjde dobře, mohl by se objevit v ES2019 nebo ES10. To však neznamená, že jej dnes nemůžete použít. TypeScript i Babel podporují pole tříd. Pokud tedy použijete jedno z těchto, můžete také okamžitě začít používat pole třídy.

Soukromá pole a metody

Jak jsme probrali, všechny vlastnosti třídy nebo pole jsou ve výchozím nastavení veřejná. Každý k nim tedy může přistupovat a upravovat je. To nemusí být žádoucí ve všech situacích. Někdy můžete chtít ponechat některé vlastnosti, aby byly skryté nebo nepřístupné zvenčí třídy. To je přesně to, co soukromá pole umí.

Když deklarujete nějakou vlastnost třídy jako soukromou, můžete k ní přistupovat pouze uvnitř této třídy. Pokud tedy chcete získat přístup k této vlastnosti a změnit ji, můžete vytvořit metody uvnitř této třídy a použít je ke změně těchto vlastností. Syntaxe pro vytváření soukromých polí je jednoduchá, stačí začít název vlastnosti # . Nezapomeňte použít # když chcete k nemovitosti přistupovat.

// Class with public and private properties
class MyClass {
  // Create public property
  foo = 'This is a public property.'

  // Create private property
  // Remember to start with '#'
  #bar = 'This is a private property.'

  // Add method to access and return public property 'foo'
  getFoo() {
    return this.foo
  }

  // Add method to access and return private property 'bar'
  getBar() {
    // Remember to use full name of the property, including the '#'
    return this.#bar
  }

  // Add method to change private property 'bar'
  changeBar(text) {
    // Remember to use full name of the property, including the '#'
    this.#bar = text
  }
}

// Create instance of MyClass
const classInstanceOne = new MyClass()

// Try to log public property 'foo' with 'getFoo()' method
console.log(classInstanceOne.getFoo())
// Outputs: 'This is a public property.'

// Try to log private property 'bar' with 'getBar()' method
console.log(classInstanceOne.getBar())
// Outputs: 'This is a private property.'

// Try to log public property 'foo' directly
console.log(classInstanceOne.foo)
// Outputs: 'This is a public property.'

// Try to log private property 'bar' directly
console.log(classInstanceOne.#bar)
// Outputs: SyntaxError: Undefined private field undefined: must be declared in an enclosing class

// Use 'changeBar' method to change private property 'bar'
classInstanceOne.changeBar('This is new text.')

// Try to log private property 'bar' with 'getBar()' method again
console.log(classInstanceOne.getBar())
// Outputs: 'This is new text.'

Podobně jako u veřejných metod můžete také vytvářet metody, které jsou soukromé. Pravidla jsou stejná jako pro soukromé vlastnosti nebo pole. Tyto metody jsou viditelné pouze zevnitř třídy. Nemůžete k nim přistupovat ani je používat zvenčí. Syntaxe je také stejná. Název metody začněte znakem „#“.

// Class with private property and method
class MyClass {
  #bar = 'This is a private property.'

  // Add private method
  #getBar() {
    // Change the value of private property 'bar'
    this.#bar = 'Let\'s update this property.'
  }

  // Add public method to triggers private method 'useGetBar()'
  useGetBar() {
    this.#getBar()
  }
}

Statické vlastnosti a metody

Veřejné a soukromé vlastnosti a metody nejsou jediné, které můžete ve svém kódu použít. Třídy JavaScriptu také podporují statické vlastnosti a metody. Jeden rozdíl mezi veřejnými, soukromými a statickými vlastnostmi a metodami je ten, že statické vlastnosti a metody lze volat na třídu bez vytváření nové instance.

No, toto je vlastně jediný případ, kdy můžete použít statické vlastnosti a metody a volat je ve třídě. V instancích tříd nemůžete volat statické vlastnosti a metody. Syntaxe pro deklarování vlastnosti nebo metody jako statické je také jednoduchá. Vše, co musíte udělat, je použít static klíčové slovo před názvem vlastnosti nebo metody.

// Class with static property and method
class MyClass {
  // Declare static property
  static foo = 'My static property.'

  // Declare static method
  static getFoo() {
    // Return the value of static property 'foo'
    return MyClass.foo
  }
}

// Try to access the 'foo' static property directly on MyClass
console.log(MyClass.foo)
// Outputs: 'My static property.'

// Try to access the 'foo' static property
// using getFoo() static method on MyClass
console.log(MyClass.getFoo())
// Outputs: 'My static property.'


// Create instance of MyClass
const myClassInstance = new MyClass()

// Try to access the 'foo' static property on myClassInstance
console.log(myClassInstance.getFoo())
// Outputs: TypeError: myClassInstance.getFoo is not a function

console.log(myClassInstance.foo)
// Outputs: undefined

Vzhledem k tomu, že statické metody mohou být volány pouze na třídách, vývojáři často vytvářejí obslužné metody pro své aplikace. Můžete je například použít k vyčištění nebo aktualizacím při vytváření nových instancí třídy nebo ke zničení existujících. Totéž platí pro statické vlastnosti. Můžete je například použít k udržení počtu instancí tříd, které jste vytvořili.

class MyClass {
  // Declare static property to retain
  // the number of instances of MyClass created
  static count = 0

  constructor() {
    // Update count of MyClass instances
    // during every instantiation
    MyClass.count++;
  }

  // return number of instances of MyClass
  static getCount() {
    return MyClass.count
  }
}

// Log number of instances of MyClass
console.log(MyClass.getCount())
// Outputs: 0


// Create one instance of MyClass
const firstInstanceOfMyClass = new MyClass()

// Log number of instances of MyClass
console.log(MyClass.getCount())
// Outputs: 1


// Create another instance of MyClass
const secondInstanceOfMyClass = new MyClass()

// Log number of instances of MyClass
console.log(MyClass.getCount())
// Outputs: 2

Statické vlastnosti mohou být velmi užitečné, pokud pracujete s knihovnou React. Jak jsme diskutovali v React Best Practices &Tips, je dobrým zvykem si osvojit a používat defaultProps a prop-types . Dříve bylo možné používat statické vlastnosti v třídách JavaScriptu, které bylo nutné definovat defaultProps a prop-types mimo komponentu třídy.

Po zavedení statických vlastností to již není nutné. Nyní můžete definovat defaultProps stejně jako prop-types přímo uvnitř vašich komponent pomocí static klíčové slovo. To vám může pomoci učinit váš kód čitelnějším a čistším.

// Import React and ReactDom
import React from 'react'
import ReactDOM from 'react-dom'

// Import prop-types
import { PropTypes } from 'prop-types'

class MyClassComponent extends React.Component {
  static defaultProps = {
    name: 'Anonymous',
    age: 0
  }

  // Define prop-types for MyClassComponent
  static propTypes = {
    name: PropTypes.string,
    age: PropTypes.number.isRequired
  }

  render() {
    return(
      <div>{this.props.name} ({this.props.age})</div>
    )
  }
}

Rekvizity nejsou jedinými dobrými kandidáty pro použití static klíčové slovo. Ve skutečnosti jsou v Reactu některé metody životního cyklu, které dokonce vyžadují použití static klíčové slovo. Například getDerivedStateFromProps() a getDerivedStateFromError() .

Přístupové prvky pro získání a nastavení

Dalším relativně novým přírůstkem do tříd JavaScriptu jsou přístupové objekty getter a setter. Tyto dva byly představeny v ES5. Tato jména mohou znít, jako bychom mluvili o nějakém složitém tématu. Nic nemůže být dále od pravdy. Přístupové prvky Getter a Setter jsou ve skutečnosti velmi jednoduché na pochopení i použití.

Zjednodušeně řečeno, getry a settery jsou metody, které umožňují zpracovat data před přístupem nebo nastavením hodnot vlastností. Metody setter se používají, když chcete nastavit nebo definovat hodnoty vlastností. Můžete například použít metodu setter k ověření nějaké hodnoty, než ji váš program povolí použít jako hodnotu vlastnosti.

Dále getry. Gettry jsou metody, které používáte, když chcete získat přístup k hodnotě vlastnosti a/nebo ji vrátit. Například, když chcete získat přístup k nějaké hodnotě vlastnosti, nemusíte tuto hodnotu jednoduše vracet. Místo toho můžete použít metodu getter k definování „vlastního“ výstupu, jako je například krátká zpráva, která obsahuje tuto hodnotu vlastnosti.

Chcete-li vytvořit přístupový objekt setter, přidejte před název metody set . Když chcete vytvořit přístupový objekt getter, použijete předponu get .

class User {
  constructor(username) {
    // This will invoke the setter
    this.username = username
  }

  // Create getter for 'username' property
  get username() {
    console.log(`Your username is ${this._username}.)
  }

  // Create setter for 'username' property
  set username(newUsername) {
    // Check for the newUsername length
    if (newUsername.length === 0) {
      // Show a message if username is too short
      console.log('Name is too short.')
    }

    // Otherwise, accept the newUsername and use it as a value for 'username'
    this._username = newUsername
  }
}

// Create instance of User
const userOne = new User('Stuart')

// Access the username property of userOne
// This will automatically invoke the getter method for 'username' property
userOne.username
// Outputs: 'Your username is Stuart.'

// Try to create instance of User without username
// This will automatically invoke the setter method for 'username' property
const userTwo = new User('') // 'Name is too short.'

Ve výše uvedeném příkladu jsme vytvořili getter a setter pro vlastnost username. Jak vidíte, před vlastnost uživatelského jména jsme přidali _ . Bez toho pokaždé get nebo set volaná metoda by způsobila přetečení zásobníku. To znamená get by bylo voláno a to by způsobilo get být volán znovu a znovu. To by vytvořilo nekonečnou smyčku.

Dvě věci o funkcích getter a setter, které potřebujete vědět. Za prvé, nevoláte je explicitně. Vše, co musíte udělat, je pouze je definovat. JavaScript udělá zbytek práce za vás. Druhá věc je, že metoda setter a getter musí mít stejný název jako vlastnost, kterou chcete zpracovat.

To je důvod, proč jsme ve výše uvedeném příkladu použili „username“ jako název pro naše metody setter a getter. Toto spolu s get a set klíčová slova, říká JavaScriptu, co by se mělo dělat a s jakou vlastností by se to mělo dělat. Ujistěte se tedy, že názvy metod setter a getter vždy odpovídají jménům vlastností.

class Cat {
  constructor(name, age) {
    // Automatically invokes setters for 'name' and 'age'
    this.name = name
    this.age = age
  }

  // Create getter for 'name' property
  get name() {
    console.log(`My name is ${this._name}.`)
  }

  // Create getter for 'age' property
  get age() {
    console.log(`My age is ${this._age}.`)
  }

  // Create setter for 'name' property
  set name(newName) {
    if (newName.length === 0) {
      console.log('Name must contain at least one character.')
    }

    this._name = newName
  }

  // Create setter for 'age' property
  set age(newAge) {
    if (typeof newAge !== 'number') {
      console.log('Age must be a number.')
    }

    this._age = newAge
  }
}

// Create instance of Cat
const doris = new Cat('Doris', 2)

// Access doris' name
// Automatically invokes getter for 'name' property
doris.name
// Outputs: 'My name is Doris.'

// Access doris' age
// Automatically invokes getter for 'age' property
doris.age
// Outputs: 'My age is 2.'

Mixiny

V předchozí části jste se dozvěděli o dědičnosti třídy a o tom, jak funguje rozšíření. Problém je v tom, že v JavaScriptu mohou objekty dědit pouze z jednoho objektu. V případě tříd JavaScriptu může jedna třída rozšířit pouze jednu další třídu. To je, když chcete, aby jedna třída dědila z jiné třídy.

Co když však chcete nebo potřebujete, aby jedna třída dědila z více tříd? Co když chcete udělat něco jako class One extends Two and Three ? No, jsou tu špatné a dobré zprávy. Špatná zpráva je, že třídní dědičnost to neumožňuje. Dobrou zprávou je, že na tom nezáleží, protože existuje řešení.

Přestože třídy JavaScriptu nepodporují použití extend s více třídami podporují něco jiného. Tomuto něčemu se říká mixiny. Co jsou to mixiny? Jednoduše řečeno, mixiny vám umožňují vzít jednu třídu a nechat ji rozšířit nebo zdědit z více tříd. Nejlepší část? Mixiny jsou velmi snadno pochopitelné, vytvářejí a také používají.

Když chcete vytvořit nový mixin, nemusíte používat žádnou speciální syntaxi ani klíčové slovo. Mixiny definujete jednoduše jako funkci, která přijímá nadtřídu jako parametr a vytváří z ní novou podtřídu. Když chcete použít mixin, použijte jej s extend klíčové slovo, tj. class MyClass extends MyMixin(MySuperclass) {} . Nezapomeňte předat supertřídu jako argument.

// Create mixin
const MyMixin = (superclass) => class extends superclass {
  // Add some method all classes inheriting
  // from this mixin will inherit, and be able to use.
  sayHi() {
    console.log('Hi!')
  }
}

// Create Human superclass
class Human {
  isHuman = true
}

// Use mixin to create class Man and let it inherit from Human
// 1) Class has to extend the MyMixin mixin and
// 2) Pass the superclass as an argument to mixin
class Man extends MyMixin(Human) {
  isMan = true
}

// Create instance of Man class
const jack = new Man()

// Log the value of 'isMan' property
console.log(jack.isMan)
// Outputs: true

// Log the value of 'isHuman' property (inherited from Human)
console.log(jack.isHuman)
// Outputs: true

// Call 'sayHi()' method inherited from 'MyMixin' mixin
jack.sayHi()
// Outputs: 'Hi!'

Kombinování mixů

Jak vidíte, práce s mixiny je velmi snadná. A to není vše, co můžete s mixiny dělat. Můžete také použít více mixinů, tj. nechat třídu zdědit z více mixinů. Chcete-li to provést, předáte jeden mixin jako argument do jiného mixinu. Potom předáte supertřídu jako argument úplně poslednímu mixinu. Na pořadí mixinů nezáleží.

// Create first mixin
const MyMixinOne = (superclass) => class extends superclass {
  sayHi() {
    console.log('Hi!')
  }
}

// Create second mixin
const MyMixinTwo = (superclass) => class extends superclass {
  getSomeZzz() {
    console.log('Zzzzz...')
  }
}

// Create third mixin
const MyMixinThree = (superclass) => class extends superclass {
  getWorkout() {
    console.log('Building some muscles...')
  }
}

// Create class superclass
class Human {
  isHuman = true
}

// Create class Man and let it inherit from all Mixins
// Note 1: the order of mixins really doesn't matter.
// Note 2: Make sure to pass the superclass as an argument to the last, innermost, mixin
class Man extends MyMixinThree(MyMixinTwo(MyMixinOne(Human))) {
  isMan = true
}

// Create instance of Man class
const scott = new Man()

scott.sayHi()
// Outputs: 'Hi!'

scott.getWorkout()
// Outputs: 'Building some muscles...'

scott.getSomeZzz()
// Outputs: 'Zzzzz...'

Mixy a přepisující vlastnosti a metody

Další dobrá věc na mixinech je, že vše funguje jako v případě tříd JavaScriptu. To znamená, že můžete přepsat metody, můžete použít super klíčové slovo pro přístup k vlastnostem a metodám nadtřídy a také volání super() metoda v podtřídě constructor() .

// Create mixin
const MyMixin = (superclass) => class extends superclass {
  // Add public method to print message with gender (defined in superclass)
  printGender() {
    console.log(`My gender is ${this.gender}.`)
  }
}

// Create Human superclass
class Human {
  // Add some public properties
  isHuman = true
  gender = undefined
}

// Create class Man
class Man extends MyMixin(Human) {
  // Override Human's gender property
  gender = 'Male'
}

// Create class Woman
class Woman extends MyMixin(Human) {
  // Override Human's gender property
  gender = 'Female'

  // Override 'printGender()' method
  printGender() {
    // Call the original 'printGender()' method defined in mixin
    super.printGender()

    // Create new message for Woman class
    console.log(`I am a ${this.gender}.`)
  }
}

// Create instance of Man class
const andreas = new Man()

// Print gender of andreas instance
andreas.printGender()
// Outputs: 'My gender is Male.'

// Create instance of Man class
const victorie = new Woman()

// Print gender of victorie instance
victorie.printGender()
// Outputs:
// 'My gender is Female.' (invoked by calling 'super.printGender()')
// 'I am a Female.' (new message)

Mixiny jako plány třídy

Další způsob, jak přemýšlet o mixinech, je z hlediska šablon pro třídy JavaScriptu. To je dále podpořeno skutečností, že do mixinů můžete přidávat vlastnosti a metody a nechat je všechny zdědit své třídy. Můžete tedy také použít mixiny tímto způsobem, jako návrh pro vaše třídy JavaScript.

Kromě toho můžete mixiny používat také jako úložiště vlastností a metod, které chcete sdílet s různými třídami. Proč se opakovat nebo vytvářet dlouhé řetězce třídní dědičnosti? Vložte všechny vlastnosti a metody, které chcete sdílet, do mixinu a nechte své třídy, aby z něj dědily.

// Create mixin with shared properties and methods
const MyMixin = (superclass) => class extends superclass {
  someSharedProperty = 'Foo'
  anotherSharedProperty = 13

  someShareMethod() {
    // Do something
  }

  anotherShareMethod() {
    // Do something
  }
}

// Create various different superclasses
class MySuperclassOne {}

class MySuperclassTwo {}

class MySuperclassThree {}

// Create various different subclasses, all sharing properties and methods defined in mixin
class MySubclassOne extends MyMixin(MySuperclassOne) {}

class MySubclassTwo extends MyMixin(MySuperclassTwo) {}

class MySubclassThree extends MyMixin(MySuperclassThree) {}

Epilolog:Třídy JavaScriptu – přátelský úvod Pt.2

To je ono! Právě jste dokončili druhou a poslední část této minisérie zaměřené na třídy JavaScriptu. Nyní víte vše, co potřebujete vědět o třídách JavaScript. Co teď? Zopakujte si, co jste se naučili. Vraťte se k příkladům, se kterými jsme pracovali, hrajte si s nimi a vytvořte si vlastní. Ujistěte se, že opravdu všemu rozumíte.