实现react教程
仅实现下棋交互功能 https://codepen.io/gaearon/pen/VbbVLg?editors=0010
main.html
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>JS Bin</title><style>body {font: 14px "Century Gothic", Futura, sans-serif;margin: 20px;}ol, ul {padding-left: 30px;}.board-row:after {clear: both;content: "";display: table;}.status {margin-bottom: 10px;}.square {background: #fff;border: 1px solid #999;float: left;font-size: 24px;font-weight: bold;line-height: 34px;height: 34px;margin-right: -1px;margin-top: -1px;padding: 0;text-align: center;width: 34px;}.square:focus {outline: none;}.kbd-navigation .square:focus {background: #ddd;}.game {display: flex;flex-direction: row;}.game-info {margin-left: 20px;}</style></head><body><div id="root"></div><script src="./dist/main.js"></script></body></html>
tic.js
import {render, Component, createElement} from './toy-react.js'// window.a = <div id="app" class="head"></div>class Square extends Component {constructor(props) {super(props);this.state = {value: null,};}render() {return (<buttonclassName="square"onClick={() => this.setState({value: 'X'})}>{this.state.value}</button>);}}class Board extends Component {renderSquare(i) {return <Square />;}render() {const status = 'Next player: X';return (<div><div className="status">{status}</div><div className="board-row">{this.renderSquare(0)}{this.renderSquare(1)}{this.renderSquare(2)}</div><div className="board-row">{this.renderSquare(3)}{this.renderSquare(4)}{this.renderSquare(5)}</div><div className="board-row">{this.renderSquare(6)}{this.renderSquare(7)}{this.renderSquare(8)}</div></div>);}}class Game extends Component {render() {return (<div className="game"><div className="game-board"><Board /></div><div className="game-info"><div>{/* status */}</div><ol>{/* TODO */}</ol></div></div>);}}// ========================================render(<Game />,document.getElementById('root'));
toy-react.js
const RENDER_TO_DOM = Symbol('render to dom')export class Component {constructor() {this.props = Object.create(null);this.children = [];this._root = null;this._range = null;}setAttribute(name, value) {if (name == 'onClick') {console.log(name)}this.props[name] = value}appendChild(component) {this.children.push(component)}[RENDER_TO_DOM](range) {this._range = rangethis.render()[RENDER_TO_DOM](range)}rerender() {// this._range.deleteContents()// this[RENDER_TO_DOM](this._range)// 先插入再删除let oldRange = this._range;let range = document.createRange();range.setStart(oldRange.startContainer, oldRange.startOffset)range.setEnd(oldRange.startContainer, oldRange.startOffset)this[RENDER_TO_DOM](range)oldRange.setStart(range.endContainer, range.endOffset)oldRange.deleteContents()}setState (newState) {// console.log(newState)// 没有state 或者 不是对象, 短路if (this.state == null || typeof this.state != "object") {this.setState = newStatethis.rerender()return;}// 深拷贝let merge = (oldState, newState) => {for (let p in newState) {if (oldState[p] == null || typeof oldState[p] != "object") {oldState[p] = newState[p]} else {merge(oldState[p], newState[p])}}}merge(this.state, newState)this.rerender()}// get root() {// if (!this._root) {// this._root = this.render().root// }// return this._root// }}class ElementWrapper {constructor(type) {this.root = document.createElement(type)}setAttribute(name, value) {// 过滤事件, 如onClickif (name.match(/^on([\s\S]+)$/)) {// console.log(RegExp.$1)// 绑定事件, Click转clickthis.root.addEventListener(RegExp.$1.replace(/^[\s\S]/, c => c.toLowerCase()), value)} else if (name == 'className'){this.setAttribute('class', value)} else {this.root.setAttribute(name, value)}this.root.setAttribute(name, value)}appendChild(component) {// this.root.appendChild(component.root)let range = document.createRange()range.setStart(this.root, this.root.childNodes.length)range.setEnd(this.root, this.root.childNodes.length)component[RENDER_TO_DOM](range)}[RENDER_TO_DOM](range){range.deleteContents()range.insertNode(this.root)}}class TextWrapper {constructor(content) {this.root = document.createTextNode(content)}[RENDER_TO_DOM](range){range.deleteContents()range.insertNode(this.root)}}export function createElement(tagName, attributes, ...rest) {let elementif (typeof tagName == 'string') {element = new ElementWrapper(tagName)} else {element = new tagName}if (typeof attributes === 'object' && attributes instanceof Object) {for (const key in attributes) {element.setAttribute(key, attributes[key])}}let insertChildren = (children) => {// console.log(children)for(const child of children) {if (typeof child == 'string') {child = new TextWrapper(child)}if (child === null) {continue;}if ((typeof child == 'object') && (child instanceof Array)) {insertChildren(child)} else {element.appendChild(child)}}}insertChildren(rest)return element}export function render(component, parentElement) {// parentElement.appendChild(component.root)let range = document.createRange()range.setStart(parentElement, 0)range.setEnd(parentElement, parentElement.childNodes.length)component[RENDER_TO_DOM](range)}
终极交互 https://codepen.io/gaearon/pen/gWWZgR?editors=0010
不支持函数组件, 需要修改为类组件
tic.final.js
import {render, Component, createElement} from './toy-react.js'class Square extends Component {render() {return (<button className="square" onClick={this.props.onClick}>{this.props.value}</button>);}}class Board extends Component {renderSquare(i) {return (<Squarevalue={this.props.squares[i]}onClick={() => this.props.onClick(i)}/>);}render() {return (<div><div className="board-row">{this.renderSquare(0)}{this.renderSquare(1)}{this.renderSquare(2)}</div><div className="board-row">{this.renderSquare(3)}{this.renderSquare(4)}{this.renderSquare(5)}</div><div className="board-row">{this.renderSquare(6)}{this.renderSquare(7)}{this.renderSquare(8)}</div></div>);}}class Game extends Component {constructor(props) {super(props);this.state = {history: [{squares: Array(9).fill(null)}],stepNumber: 0,xIsNext: true};}handleClick(i) {const history = this.state.history.slice(0, this.state.stepNumber + 1);const current = history[history.length - 1];const squares = current.squares.slice();if (calculateWinner(squares) || squares[i]) {return;}squares[i] = this.state.xIsNext ? "X" : "O";this.setState({history: history.concat([{squares: squares}]),stepNumber: history.length,xIsNext: !this.state.xIsNext});}jumpTo(step) {this.setState({stepNumber: step,xIsNext: (step % 2) === 0});}render() {const history = this.state.history;const current = history[this.state.stepNumber];const winner = calculateWinner(current.squares);const moves = history.map((step, move) => {const desc = move ?'Go to move #' + move :'Go to game start';return (<li key={move}><button onClick={() => this.jumpTo(move)}>{desc}</button></li>);});let status;if (winner) {status = "Winner: " + winner;} else {status = "Next player: " + (this.state.xIsNext ? "X" : "O");}return (<div className="game"><div className="game-board"><Boardsquares={current.squares}onClick={i => this.handleClick(i)}/></div><div className="game-info"><div>{status}</div><ol>{moves}</ol></div></div>);}}// ========================================render(<Game />, document.getElementById("root"));function calculateWinner(squares) {const lines = [[0, 1, 2],[3, 4, 5],[6, 7, 8],[0, 3, 6],[1, 4, 7],[2, 5, 8],[0, 4, 8],[2, 4, 6]];for (let i = 0; i < lines.length; i++) {const [a, b, c] = lines[i];if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {return squares[a];}}return null;}
