React

O que é?

Uma biblioteca para descrever interfaces de usuário que reagem a dados. 

Características

  • Declarativo
  • Biblioteca, não framework
  • Baseado em componentes
  • Genérico: a DOM é apenas mais um deles

Histórico

  • Primeira fase
    • Web design que assume o tamanho da tela
    • Sem linguagem script padrão para interatividade na web
    • O ECMAScript nasce e tenta usar a popularidade do Java pra crescer, se tornando conhecido como Javascript
    • Ainda assim, JS é pouco padronizado
    • Além disso, JS é para manipulações simples

Histórico

  • Segunda fase
    • jQuery padroniza e facilita a manipulação da DOM
    • jQuery explode em popularidade e leva consigo o JS
    • Com o Bootstrap e o jQuery UI, tem-se a primeira 'componentização' da web
    • JS deixa de ser usado apenas para animações ou interações simples, aplicações web crescem e jQuery começa a não ser suficiente

Histórico

  • Terceira fase
    • Nasce o Backbone, que abstrai completamente a manipulação da DOM de aplicações JS
    • Popularizam-se as SPAs (Single Page Applications)
    • JS explode, esforços são feitos para a padronização, nascem Node, Angular, React, Electron
    • Novas versões do JS são lançadas regularmente

Porque não

jQuery?

Em suma:

  • Baseado em DOM
  • Paradigma imperativo
  • Gestão de complexidade

Vamo brincar de benchmark? :D

class Tasks {
	constructor() {
		try {
			this._tasks = JSON.parse(window.localStorage.getItem("tasks")) || [];
		} catch (err) {
			this._tasks = [];
		}
	}

	get tasks() { return [ ...this._tasks ] };
	set tasks(tasks) {
		this.setTasks(tasks);
	}
	
	async setTasks(tasks) {
		this._tasks = tasks;
		window.localStorage.setItem("tasks", tasks);
		const time = await updateUI(this._tasks, tasks);
		return time;
	}

	async addTask(content) {
		return await this.setTasks(this.tasks.concat({ id: uuid(), content, checked: false }));
	}
}

const tasksManager = new Tasks();

function updateUI() {
	$("#task-table tbody").empty();
	for (const task of tasksManager.tasks) {
		$("#task-table tbody").append(`
				<tr>
					<td><input type="checkbox" ${task.checked ? "checked": ""} id="checkbox-${task.id}"></td>
					<td>${task.content}</td>
					<td><button id="delete-${task.id}">Excluir</button></td>
				</tr>
		`);
		$(`#checkbox-${task.id}`).click(() => checkTask(task.id));
		$(`#delete-${task.id}`).click(() => deleteTask(task.id));
	}
	
	return new Promise((resolve) => setTimeout(resolve, 1));
}

function checkTask(id) {
	const task = tasksManager.tasks.find(t => t.id === id);
	task.checked = !task.checked;
	tasksManager.tasks = tasksManager.tasks;
}

function deleteTask(id) {
	tasksManager.tasks = tasksManager.tasks.filter(t => t.id !== id);
}

function addTask() {
	const content = prompt("Que tarefa você deseja adicionar?");
	if (content) {
		tasksManager.addTask(content);
	}
}

function logspace(min, max, steps) {
	const range = Math.log(max) - Math.log(min);
	return [...new Array(steps)].map((_, i) => Math.exp(Math.log(min) + i * range / (steps - 1)));
}

let logs = "";
function logTime(n) {
	if (n) {
		logs += n.toFixed(3).replace(".", ",") + "\n";
	} else {
		console.log(logs);
		logs = "";
	}
}

async function stressTest(addTask, clearTasks, betweenTests) {
		const sizes = logspace(1, 100, 10).map(Math.round);
		const testSampleSize = 3;
		
		const beforeAll = window.performance.now();
	
		const times = [];
		for (const size of sizes) {
			const before = window.performance.now();

			for (const _ of arr(testSampleSize)) {
				clearTasks();
				for (const __ of arr(size)) {
					await addTask(uuid());
				}
			}
			
			times.push(window.performance.now() - before);
			
			betweenTests && betweenTests();
		}
	
		const results = sizes.map((s, i) => ({[s]: times[i]})).reduce((acc, v) => Object.assign(acc, v), {});
		const means = sizes.map((s, i) => ({[s]: times[i] / testSampleSize / s})).reduce((acc, v) => Object.assign(acc, v), {});
		const totalTime = window.performance.now() - beforeAll;
		const mean = totalTime / testSampleSize / sizes.reduce((acc, v) => acc + v, 0);
	
		console.log("sizes");
		sizes.forEach(s => (logTime(s), logTime(s)));
		logTime(null);
		console.log("=====");
		
		logTime(totalTime);
		logTime(mean);
		logTime(null);
		
		for (const size of sizes) {
			// console.log("size " + size);
			logTime(results[size]);
			logTime(means[size]);
		}
		logTime(null);
	};

function arr(n) {
	const arr = [];
	for (let i = 0; i < n; i++) {
		arr.push(i);
	}
	
	return arr;
}

jQuery.fn.flush = function () { jQuery(this.context).find(this.selector); }

$(() => {
	$("#add-task-button").click(addTask);
	$("#stress-test").click(() => stressTest(
		() => tasksManager.addTask(uuid()),
		() => tasksManager.tasks = [],
		() => $("body").flush(),
	));
});
function arr(n) {
	const arr = [];
	for (let i = 0; i < n; i++) {
		arr.push(i);
	}
	
	return arr;
}

function logspace(min, max, steps) {
	const range = Math.log(max) - Math.log(min);
	return arr(steps).map((_, i) => Math.exp(Math.log(min) + i * range / (steps - 1)));
}

let logs = "";
function logTime(n) {
	if (n) {
		logs += n.toFixed(3).replace(".", ",") + "\n";
	} else {
		console.log(logs);
		logs = "";
	}
}

async function stressTest(addTask, clearTasks) {
	const sizes = logspace(1, 100, 10).map(Math.round);
	const testSampleSize = 2E1;
		
	const beforeAll = window.performance.now();
	
	const times = [];
	for (const size of sizes) {
		const subTimes = [];
		const before = window.performance.now();

		for (const _ of arr(testSampleSize)) {
			clearTasks();
			let time = 0;
			
			for (const __ of arr(size)) {
				await addTask(uuid());
			}

			subTimes.push(time);
		}
			
		times.push(window.performance.now() - before);
	}
	
	const results = sizes.map((s, i) => ({[s]: times[i]})).reduce((acc, v) => Object.assign(acc, v), {});
	const means = sizes.map((s, i) => ({[s]: times[i] / testSampleSize / s})).reduce((acc, v) => Object.assign(acc, v), {});
	const totalTime = window.performance.now() - beforeAll;
	const mean = totalTime / testSampleSize / sizes.reduce((acc, v) => acc + v, 0);
	
	console.log("sizes");
	sizes.forEach(s => (logTime(s), logTime(s)));
	logTime(null);
	console.log("=====");
		
	logTime(totalTime);
	logTime(mean);
	logTime(null);
		
	for (const size of sizes) {
		// console.log("size " + size);
		logTime(results[size]);
		logTime(means[size]);
	}
	logTime(null);
};

class Todo extends React.Component {
	constructor(props, context) {
		super(props, context);
		this.state = { tasks: [] };
	}
	
	addTask(content) {
		return new Promise((resolve) => {
			const before = window.performance.now();
			this.setState({
				tasks: (this.state.tasks || []).concat({
					id: uuid(),
					content,
					checked: false,
				}),
			}, () => setTimeout(() => resolve(window.performance.now() - before)), 1);
		});
	}
	
	deleteTask(id) {
		this.setState({
			tasks: (this.state.tasks || []).filter(t => t.id !== id),
		});
	}
	
	checkTask(id) {
		this.setState({
			tasks: (this.state.tasks || []).map(t => {
				if (t.id !== id) return t;
				else return { ...t, checked: !t.checked };
			}),
		});
	}
	
	clearTasks() {
		this.setState({ tasks: [] });
	}

	render() {
		return (
			<div>
				<button id="stress-test" onClick={() => stressTest(this.addTask.bind(this), this.clearTasks.bind(this))}>
					Teste de estresse
				</button>
				<div className="root">
					<button id="add-task-button" onClick={() => this.addTask(prompt("Digite a tarefa a ser inserida"))}>
						Adicionar tarefa
					</button>
					<table id="task-table">
						<thead>
							<tr>
								<th>Feito</th>
								<th>Nome</th>
								<th></th>
							</tr>
						</thead>
						<tbody>
							{
								this.state.tasks.map(task => (
									<tr>
										<td>
											<input type="checkbox" onChange={
												() => this.checkTask(task.id)
											}/>
										</td>
										<td>{task.content}</td>
										<td>
											<button onClick={
												() => this.deleteTask(task.id)
											}>
												Deletar
											</button>
										</td>
									</tr>
								))
							}
						</tbody>
					</table>
				</div>
			</div>
		);
	}
}

ReactDOM.render(<Todo/>, document.querySelector("body"));

Performance

Performance

Performance

Algoritmo

Com o jQuery, toda mudança é feita diretamente na DOM, cuja manipulação é custosa. Além disso, existem memory leaks conhecidos.

Já o React faz as manipulações numa estrutura de dados que representa a DOM (a Virtual DOM), determina o que foi modificado e reconcilia o layout com sua reconciliação com o mínimo possível de operações.

Algoritmo

JSX

e Virtual DOM

Targets

  • React implementa uma estrutura de dados feita para descrever interfaces de usuário
  • Portanto, para transformar a descrição em layout, precisa-se de alguma outra biblioteca. Exemplos:
    • HTML: ReactDOM
    • Sketch: ReactSketchApp
    • Mobile: ReactNative
    • Realidade Virtual: ReactVR
    • Até windows!

Propriedades

e Estado

Ciclo

de Vida

React

By Victor Magalhães