Kliensoldali webprogramozás
Horváth Győző
Egyetemi docens
1117 Budapest, Pázmány Péter sétány 1/c., 2.408-as szoba
Tel: (1) 372-2500/8469
horvath.gyozo@inf.elte.hu
import {html, render} from 'https://unpkg.com/lit-html?module';
export class FormView {
constructor(✒>game, render<✒) { ✒>// data coming from outside<✒
this.game = game
this.renderAll = render
this.onGenerate = this.onGenerate.bind(this)
}
✒><✒onGenerate(e) { ✒>// event handler<✒
e.preventDefault()
const n = document.querySelector('#n').valueAsNumber
const m = document.querySelector('#m').valueAsNumber
this.game.initBoard(n, m)
this.renderAll()
}
✒>render<✒(gameState, n, m) { ✒>// updating the UI in a declarative way<✒
return html`
<form action="">
n = <input type="number" id="n" value="${gameState === 0 ? 3 : n}"> <br>
m = <input type="number" id="m" value="${gameState === 0 ? 4 : m}">
<button type="button" @click=${this.onGenerate}>Start new game</button>
</form>
`
}
}
// functional component
function HelloMessage(props) {
return <div>Hello {props.name}!</div>;
}
ReactDOM.render(<HelloMessage name="Győző" />, mountNode);
export function App() {
return (
<div className="container">
<Card />
</div>
)
}
export function Card() {
return (
<div className="card text-center">
<CardHeader />
<div className="card-body">
<FilterForm />
<FilterList />
</div>
<CardFooter />
</div>
)
}
Statikus → dinamikus
const content = 'Something'
const isAdmin = false
const items = [1, 2, 3]
const color = 'orange'
// content
<div>Some {content}</div>
// elements: always close the tag
<input />
// attributes
<label htmlFor="foo" className="bar" tabIndex="10" />
<div data-attr={content}>Something}</div>
// style attributes
<div style={{backgroundColor: color}}></div>
// conditional
<div>
{ isAdmin
? <AdminPanel />
: <UserPanel /> }
</div>
// or
const panel = isAdmin ? <AdminPanel /> : <UserPanel />
{panel}
// loops
<ul>
{items.map(e => <li>{e}</li>)}
</ul>
export function FilterList() {
const titles = [ 'title1', 'title2', 'title3' ]
const listItems = titles.map(title => <FilterListItem />)
return (
<ul id="code-list" className="list-group">
{listItems}
</ul>
)
}
export function FilterListItem() {
const title = 'A title'
return (
<li className="list-group-item">{title}</li>
)
}
export function FilterForm() {
const filterText = 'apple'
return (
<form className="form-inline">
<label htmlFor="inlineFormInputName2">Filter</label>
<input value={filterText} type="text" className="form-control m-2" id="text-filter" />
{/* <input defaultValue={filterText} type="text" className="form-control m-2" id="text-filter" /> */}
</form>
)
}
Megj.: a value
fix, a komponens az adat
leképezése, így a beleírt érték sem változhat. Szerkesztéshez a
defaultValue
-t kell használni
Kívülről kapott adatok
// actual parameter
<Hello name="Győző">
// formal parameter
function Hello(props) {
return <h1>{props.name}</h1>
}
// formal parameter, object destructuring
function Hello({ name }}) {
return <h1>{name}</h1>
}
npm install --save prop-types
import PropTypes from 'prop-types';
function Hello({ name = 'Anonymous' }) {
return <h1>{name}</h1>
}
Hello.propTypes = {
name: PropTypes.string
}
// ✒>TypeScript<✒
function Hello({ name✒>: string<✒ = 'Anonymous' }) {
return <h1>{name}</h1>
}
export function FilterList() {
const titles = [ 'title1', 'title2', 'title3' ]
const listItems = titles.map(title => <FilterListItem ✒>title={title}<✒ />)
return (
<ul id="code-list" className="list-group">
{listItems}
</ul>
)
}
export function FilterListItem(✒>props<✒) {
return (
<li className="list-group-item">✒>{props.title}<✒</li>
)
}
// or
export function FilterListItem(✒>{title}<✒) {
return (
<li className="list-group-item">✒>{title}<✒</li>
)
}
export function Card() {
const titles = [ 'title1', 'title2', 'item3' ]
const filterText = 'title'
return (
<div className="card text-center">
<CardHeader />
<div className="card-body">
<FilterForm filterText={filterText} />
<FilterList filterText={filterText} list={titles} />
</div>
<CardFooter />
</div>
)
}
export function FilterList({list, filterText}) {
const titles = list.filter(e => e.includes(filterText))
const listItems = titles.map(title => <FilterListItem title={title} />)
return (
<ul id="code-list" className="list-group">
{listItems}
</ul>
)
}
Belső állapot
React.render()
direkt hívásuseState
import React, { useState } from "react";
const randomColor = () =>`hsl(${random(0, 360)}, 50%, 50%)`
function ColorBox() {
✒>const [color, setColor] = useState(randomColor())<✒
return (
<div style={{ backgroundColor: ✒>color<✒ }} onClick={() => ✒>setColor(randomColor())<✒}>
React rendered box, random value: {random(1, 100)}
</div>
)
}
Alkalmazásszintű adat
function Card() {
const [filterText, setFilterText] = useState('');
const [titles, setTitles] = useState([ 'title1', 'title2', 'item3' ]);
return (
<div className="card text-center">
<CardHeader />
<div className="card-body">
<FilterForm filterText={filterText} />
<FilterList filterText={filterText} list={titles} />
</div>
<CardFooter />
</div>
)
}
function MyComponent() {
function handleClick(e) {
console.log('Button was clicked')
}
return (
<button ✒>onClick={handleClick}<✒>Click</button>
)
}
const random = (a, b) => Math.floor(Math.random() * (b - a + 1)) + a;
const randomColor = () =>`hsl(${random(0, 360)}, 50%, 50%)`
function ColorBox(props) {
const [color, setColor] = useState(randomColor())
const changeColor = () => {
setColor(randomColor())
}
render() {
return (
<div style={{ backgroundColor: color }} onClick={changeColor}>
React rendered box, random value: {random(1, 100)}
</div>
)
}
}
Controlled form component
function FilterForm({ filterText: initialFilterText }) {
const [filterText, setFilterText] = useState(initialFilterText)
const handleInput = e => {
setFilterText(e.target.value)
}
return (
<form className="form-inline">
<label htmlFor="inlineFormInputName2">Filter</label>
<input value={filterText} onChange={this.handleInput} type="text" className="form-control m-2" />
</form>
)
}
// Card.js
function Card() {
// Component state
const [filterText, setFilterText] = useState('');
const [titles, setTitles] = useState([ 'title1', 'title2', 'item3' ]);
// Computed values
const filteredTitles = titles.filter(e => e.includes(filterText))
// Event handlers
const changeFilterText = newVal => setFilterText(newVal)
return (
<div className="card text-center">
<CardHeader />
<div className="card-body">
<FilterForm filterText={filterText} ✒>onFilterTextChange={changeFilterText}<✒ />
<FilterList list={filteredTitles} />
</div>
<CardFooter />
</div>
)
}
// FilterForm.js
function FilterForm({filterText, ✒>onFilterTextChange<✒}) {
return (
<form className="form-inline">
<label htmlFor="inlineFormInputName2">Filter</label>
<input value={filterText} ✒>onChange={e => onFilterTextChange(e.target.value)}<✒ type="text" className="form-control m-2" />
</form>
)
}
key
attribútumid
export function FilterList({list: titles}) {
const listItems = titles.map(title => <FilterListItem ✒>key={title}<✒ title={title} />)
return (
<ul id="code-list" className="list-group">
{listItems}
</ul>
)
}
useRef
function TextInputWithFocusButton() {
✒>const inputEl = useRef(null);<✒
const onButtonClick = () => {
// `current` points to the mounted text input element
✒>inputEl.current<✒.focus();
};
return (
<>
<input ✒>ref={inputEl}<✒ type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
function FilterForm({ filterText, onFilterTextChange }) {
const inputEl = useRef(null);
useEffect(() => {
inputEl.current.focus()
}, [])
return (
<form className="form-inline">
<label htmlFor="inlineFormInputName2">Filter</label>
<input
value={filterText}
onInput={(e) => onFilterTextChange(e.target.value)}
ref={inputEl}
type="text" className="form-control m-2" id="text-filter"
/>
</form>
);
}
defaultValue
function NameForm() {
const inputEl = useRef(null);
const handleSubmit = (event) => {
alert('A name was submitted: ' + inputEl.current.value);
event.preventDefault();
}
return (
<form onSubmit={handleSubmit}>
<label>
Name:
<input type="text" ref={inputEl} defaultValue="default-text" />
</label>
<input type="submit" value="Submit" />
</form>
);
}
import 'bootstrap/dist/css/bootstrap.css'
className="active"
style={{ top: '10px' }}
classnames
csomagimport "./my-component.module.css";
classnames
csomagnpm install classnames --save
classNames('foo', 'bar'); // => 'foo bar'
classNames('foo', { bar: true }); // => 'foo bar'
classNames({ foo: true, bar: true }); // => 'foo bar'
import classnames from "classnames";
export function MyButton({ text, isBasic = true, color = "red", icon = "plus" }) {
const styles = {
button: classnames("ui", "button", {
// referencing a CSS class by name
basic: isBasic,
// dynamically referencing a CSS class
[color]: true
}),
icon: classnames("icon", icon)
};
return (
<button className={styles.button}>
<i className={styles.icon}></i>
{text}
</button>
);
}
Lokális hatókörű stílusosztályok
/* Button.module.css */
.error {
background-color: red;
}
/* another-stylesheet.css */
.error {
color: red;
}
// Button.js
import React from 'react';
import styles from './Button.module.css'; // Import css modules stylesheet as styles
import './another-stylesheet.css'; // Import regular stylesheet
class Button extends Component {
// reference as a js object
return <button className={styles.error}>Error Button</button>;
}
npm install react-router-dom
import { render } from "react-dom";
import { BrowserRouter, Routes, Route, Outlet } from "react-router-dom";
render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root")
);
function App() {
return (
<Routes>
<Route path="invoices" element={<Invoices />}>
<Route path=":invoiceId" element={<Invoice />} />
<Route path="sent" element={<SentInvoices />} />
</Route>
</Routes>
);
}
function Invoices() {
return (
<div>
<h1>Invoices</h1>
<Outlet />
</div>
);
}
function Invoice() {
let { invoiceId } = useParams();
return <h1>Invoice {invoiceId}</h1>;
}
function SentInvoices() {
return <h1>Sent Invoices</h1>;
}
Komponensek egymásba ágyazása
Öröklés helyett
Ha ismert előre a gyerekkomponens:
const App = () => <Parent />
const Parent = () =>
<div>Parent
<Child />
</div>
const Child = () => <div>Child</div>
props.children
const App = () =>
<Parent>
<Child />
</Parent>
const Parent = ({children}) =>
<div>Parent
{children}
</div>
const Child = () => <div>Child</div>
const App = () =>
<Parent children={<Child />} />
const Parent = ({children}) =>
<div>Parent
{children}
</div>
const Child = () => <div>Child</div>
const App = () =>
<Parent
slot1={<Child1 />}
slot2={<Child2 />}
/>
const Parent = ({slot1, slot2}) =>
<div>Parent
<div>{slot1}</div>
<div>{slot2}</div>
</div>
const Child1 = () => <div>Child1</div>
const Child2 = () => <div>Child2</div>
const App = () => <Specific />
const Specific = () =>
<General name="Welcome" description="Hello message" />
const General = ({name, description}) =>
<div>
<div>{name}</div>
<div>{description}</div>
</div>
const Card = () => {
const [filterText, setFilterText] = useState('title')
✒>const changeFilterText = newVal => setFilterText(newVal)<✒
return <FilterForm
✒>onFilterTextChange={this.changeFilterText}<✒
filterText={this.state.filterText} />
}
function FilterForm({filterText, ✒>onFilterTextChange<✒}) {
return <input
value={filterText}
onInput={e => ✒>onFilterTextChange(e.target.value)<✒}
type="text" />
}
render
<DataProvider render={data => (
<h1>Hello {data.target}</h1>
)}/>
const DataProvider = ({render}) => {
const innerData = 42
return render(innerData)
}
<DataProvider render={data => (
<h1>Hello {data.target}</h1>
)}/>
children
<Mouse children={mouse => (
<p>The mouse position is {mouse.x}, {mouse.y}</p>
)}/>
<Mouse>
{mouse => (
<p>The mouse position is {mouse.x}, {mouse.y}</p>
)}
</Mouse>
Mouse.propTypes = {
children: PropTypes.func.isRequired
};
const App = () =>
<Parent>
<Child />
</Parent>
const Parent = ({children}) =>
<div>Parent
{children}
</div>
const Child = () => <div>Child</div>
const App = () =>
<Parent>
{data => <Child text={data} />}
</Parent>
const Parent = ({children}) => {
const [data] = useState(42)
return <div>Parent
{children(data)}
</div>
}
const Child = ({text}) => <div>{text}</div>
class Hello extends React.Component {
render() {
return <h1>Hello world!</h1>
}
}
// formal parameter, class component
class Hello extends React.Component {
constructor(props) {
super(props)
}
render() {
return <h1>{props.name}</h1>
}
}
this.state
, this.setState
const randomColor = () =>`hsl(${random(0, 360)}, 50%, 50%)`
class ColorBox extends React.Component {
constructor(props) {
super(props)
✒>this.state = { color: randomColor() }<✒
}
changeColor = () => {
✒>this.setState({ color: randomColor() })<✒
}
render() {
return (
<div style={{ backgroundColor: ✒>this.state.color<✒ }}>
React rendered box, random value: {random(1, 100)}
</div>
)
}
}
const random = (a, b) => Math.floor(Math.random() * (b - a + 1)) + a;
const randomColor = () =>`hsl(${random(0, 360)}, 50%, 50%)`
class ColorBox extends React.Component {
constructor(props) {
super(props)
this.state = { color: randomColor() }
this.changeColor = this.changeColor.bind(this)
}
changeColor() {
this.setState({ color: randomColor() })
}
render() {
return (
<div style={{ backgroundColor: this.state.color }} onClick={this.changeColor}>
React rendered box, random value: {random(1, 100)}
</div>
)
}
}
this
kontextusa!
class ColorBox extends React.Component {
changeColor() { /* ... */ }
render() {
return (
<div onClick={e => this.changeColor(e)} />
)
}
}
class ColorBox extends React.Component {
changeColor() { /* ... */ }
render() {
return (
<div onClick={this.changeColor.bind(this)} />
)
}
}
class ColorBox extends React.Component {
constructor(props) {
this.changeColor = this.changeColor.bind(this)
}
changeColor() { /* ... */ }
render() {
return (
<div onClick={this.changeColor} />
)
}
}
class ColorBox extends React.Component {
changeColor = () => { /* ... */ }
render() {
return (
<div onClick={this.changeColor} />
)
}
}
class Hello extends React.Component {
constructor(props) {}
componentDidMount() {}
componentWillUnmount() {}
shouldComponentUpdate(nextProps, nextState) {}
componentDidUpdate(prevProps, prevState, snapshot)
}
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick = () => { this.setState({ date: new Date() }); }
render() {
return (
<div>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
class MyComponent extends React.Component {
constructor(props) {
super(props);
✒>this.myRef = React.createRef();<✒
}
someWhere() {
const node = ✒>this.myRef.current;<✒
}
render() {
return <div ✒>ref={this.myRef}<✒ />;
}
}
export class FilterForm extends React.Component {
constructor(props) {
super(props)
this.input = React.createRef()
}
componentDidMount() {
this.input.current.focus()
}
render() {
const {filterText, onFilterTextChange} = this.props
return (
<form className="form-inline">
<label htmlFor="inlineFormInputName2">Filter</label>
<input ref={this.input} value={filterText} onChange={e => onFilterTextChange(e.target.value)} type="text" className="form-control m-2" id="text-filter" />
{/* <input defaultValue={filterText} type="text" className="form-control m-2" id="text-filter" /> */}
</form>
)
}
}