版本v16.12.0
本教程为用react从头开始实现一个小游戏。和另外的react基础知识是互补的
注:关于git命令行运行和直接cmd运行,一般情况下都用cmd命令行来运行,git它只会下载node相关的
一、环境准备
1、初始化项目
需要在你的机器上安装Node >= 8.10 和 npm >= 5.6
- create-react-app脚手架创建项目: 这个create-react-app脚手架 就和vue-cli差不多的
- 创建项目报错
npm ERR! Unexpected end of JSON input while parsing near '...mist":"^1.2.0","mocha'
,解决办法为 清除npm 缓存npm cache clean --force
; 还是不行的话 全局更新npm install npm -g
- 报错
This is probably not a problem with npm. There is likely additional logging output above.
;解决方法位,删除依赖包rm -rf node_modules
,删除锁定rm package-lock.json
,删除缓存npm cache clear --force
,最后npm install
(这步也可以提到删除依赖包之前,不出错就不用删除这些了)- 报错
cross-env' ▒▒▒▒▒ڲ▒▒▒▒ⲿ▒▒▒Ҳ
。解决cnpm install -s cross-env
,安装cross-env包,使用cross-env解决跨平台设置环境变量NODE_ENV的问题- 如果全局安装过create-react-app,则需要先卸载
npm uninstall -g create-react-app
// 第一行的 npx 不是拼写错误 —— 它是 npm 5.2+ 附带的 package 运行工具
npx create-react-app my-app
cd my-app
npm start // 在package.json里面把start改成dev之后,依然可以用npm run dev开启服务器了
- 创建包含TypeScript的create-react-app项目
- 装好了之后,就可以重命名里面的js文件为tsx文件了,比如
src/index.js
至src/index.tsx
。- 下面1为创建项目:初始化的react项目,直接就有一个TypeScript 的配置文件 tsconfig.json
- 下面2为给项目安装声明文件:上面方法创建了项目之后,依然要安装声明文件
- 不用typescript初始化的react项目,是没有TypeScript 的配置文件 tsconfig.json。安装配置文件输入后面命令
npx tsc --init
// 1、初始化项目的时候,直接安装TypeScript
npx create-react-app my-app --typescript
// 2、安装声明文件
npm install --save typescript @types/node @types/react @types/react-dom @types/jest
- 弹出webpack配置文件:创建了项目之后,默认webpack的配置被隐藏在node_moudles。如果要把这些配置释放出来(弄成vue的那种结构),需要使用命令
npm run eject
,这个弹出操作是不可逆的
- 然后就可以配置端口号等webpack相关信息,并可在
scripts/start
下修改端口号- 报错
This git repository has untracked files or uncommitted changes:
。原因:因为我们使用脚手架创建一个项目的时候,自动给我们增加了一个.gitignore文件,而我们本地却没有文件仓库,所以就需要,先git init, 再git add . , 最后git commit -m "go"
2、初始化项目中的文件
- 删除掉新项目中 src/ 文件夹下的所有文件
- 在 src/ 文件夹中创建一个名为 index.css 的文件,并拷贝这些 CSS 代码 https://codepen.io/gaearon/pen/oWWQNa?editors=0100
- 在 src/ 文件夹下创建一个名为 index.tsx 的文件,并拷贝这些 JS 代码 https://codepen.io/gaearon/pen/oWWQNa?editors=0010
- 拷贝以下三行代码到 src/ 文件夹下的 index.tsx 文件的顶部,然后改下里面没类型的报错
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
二、React基础
1、react简介
React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库。使用 React 可以将一些简短、独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作“组件”
2、React.Component 的子类
这个就相当于是封装一个组件了
// Square组件
class Square extends React.Component<any,any> {
render() {
return (
<button className="square">
{/* TODO */}
</button>
);
}
}
// ShoppingList组件
class ShoppingList extends React.Component<any,any> {
renderSquare(i: any) {
return <Square value={i} />;
}
render() {
const status = 'Next player: X';
return (
<div className="shopping-list">
<h1>Shopping List for {this.props.name}</h1>
<ul>
<li>Instagram {status}</li>
<li>
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</li>
<li>Oculus</li>
</ul>
</div>
);
}
}
// 用法示例: <ShoppingList name="Mark" />
- 上面代码解析
- class组件:简单说继承React.Component 的子类就是一个react组件,或者说是一个class组件。比如,上面的ShoppingList 是一个 React 组件类,简单说就是一个react的组件;除去class组件之外还有函数组件。
- render()函数:render函数的返回值为显示在页面上的DOM树,这个就是个虚拟dom。在react中,称返回的为一个React 元素
- 父组件给子组件参数传递props:父组件给子组件传递参数依然是用 props(“props” 是 “properties” 简写);然后传递进来的参数在render方法里面,通过this.props来调用。this.props上面的属性名字,就为传进来的名字,比如上面的
this.props.name
- jsx: 上述代码的render的返回值,实际上就是用了JSX语法,就是直接写dom结构就好了。如果不用jsx语法,则需要使用
React.createElement('div')
这种来一个一个的创建,当然jsx语法最后依然会被编译成后面那种。
- jsx中使用JavaScript 表达式,只需要用一个大括号把表达式括起来。
3、阅读index.tsx的初始代码
1、父组件给子组件通过 Props 传递数据
向一个组件传值:在 Board 组件的 renderSquare 方法中,我们将代码改写成下面这样,传递一个名为 value 的 prop 到 Square 当中
class Board extends React.Component<any,any>{
renderSquare(i: any) {
return <Square value={i} />;
}
}
使用传过来的值:修改 Square 组件中的 render 方法,把 {/* TODO */} 替换为 {this.props.value},以显示上文中传入的值
class Square extends React.Component<any,any> {
render() {
return (
<button className="square">
{this.props.value}
</button>
);
}
}
2、给组件添加交互功能
添加事件处理函数:可以直接用onClick来绑定在标签上面。修改Square 组件中 render() 方法的返回值中的 button 标签
class Square extends React.Component<any,any> {
render() {
return (
<button className="square" onClick={() => alert(this.props.value)}>
{this.props.value}
</button>
);
}
}
- 组件的state
- 组件内部数据state: 每个组件中可以有state,用来存当前组件的数据,且应该被视为一个组件的私有属性。在组件的构造函数中初始化,其余地方用this.state来访问。
- this.setState(obj): 这个为修改组件中的state,
this.setState()
,传入的值为要修改的属性的一个对象- constructor: 每次调用这个组件的时候,都会先调用这个组件的constructor方法。初始化state就必须添加一个constructor。每次定义子类的构造函数时,都需要调用 super 方法,所以在所有含有构造函数的的 React 组件中,构造函数必须以 super(props) 开头。
class Square extends React.Component<any,any> {
constructor(props: any) {
super(props);
this.state = {value2: props.value};
}
render() {
return (
<button className="square" onClick={() => this.setState({value2: 'x'})}>
{this.state.value2}
</button>
);
}
}
- React Developer Tools拓展: 在 Chrome 或者 Firefox 中安装扩展 React Devtools 可以让你在浏览器开发者工具中查看 React 的组件树。https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en
- 可以用来检查 React 组件的 state 和 props,还有查看react的组件树
- 可以来判断处于哪个版本:如果处于 开发模式的网站,图标背景会变成红色;如果处于 生产版本的网站,图标背景会变成深色;
三、React常用技术
1、子组件state提升
在react中,当你遇到需要同时获取多个子组件数据,或者两个组件之间需要相互通讯的情况时,需要把子组件的 state 数据提升至其共同的父组件当中保存。之后父组件可以通过 props 将状态数据传递到子组件当中
- 子组件给父组件传递参数:在react中,也是通过自定义事件来使子组件给父组件传递参数。不过好像是不能用$emit这种来触发父组件中的自定义事件的。react中传参如下:
- 父元素中:首先一样是在使用子组件的时候绑定一个自定义事件,事件里面绑定一个事件处理函数。在 React中,有一个命名规范,通常会将自定义事件命名为 on[Event],将事件处理函数命名为 handle[Event] 这样的格式
- 子元素中:子元素中直接在this.props上面调用自定义事件就好了,而不是用this.$emit触发了。
- 注:由于在react中,父组件调用子组件的时候,写在子组件标签上面的属性都会放在子组件的props中,所以在子组件触发自定义事件的时候,就相当于是父组件调用了那个函数(因为函数调用是调用的定义的那个位置的),所以就可以向父组件传值了
// 子组件
class Square extends React.Component<any,any> {
render() {
return (
<button className="square" onClick={() => this.props.onSquareClick(this.props.index)}> // 子组件上面调用自定义事件,并且传值
{this.props.value}
</button>
);
}
}
// 父组件
class Board extends React.Component<any,any> {
constructor(props: any) {
super(props);
this.state = {
squares: [
'O', null, 'X',
'X', 'X', 'O',
'O', null, null,
]};
}
handleSquareClick(index: any) { // 父组件中定义的自定义事件的处理函数
let squares = this.state.squares.slice(0);
squares[index] = 'X';
this.setState({squares:squares});
console.log(index);
}
renderSquare(i: any) {
return <Square index={i} value={this.state.squares[i]} onSquareClick={(index:any) => this.handleSquareClick(index)}/>; // 父组件写给子组件的props
}
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>
);
}
}
2、React 中的不可变性
一般情况下,不直接修改数据,而是存一个变量,然后来修改。比如上面的
let squares = this.state.squares.slice(0);
,就是把数组存了一份,然后改的,没有直接改state。不可变性最主要的优势在于它可以帮助我们在 React 中创建 pure components。我们可以很轻松的确定不可变数据是否发生了改变,从而确定何时对组件进行重新渲染
3、函数组件
函数组件是相对于class组件来说的。使用函数组件的时候报错了。
- 函数组件不需要定义一个继承于 React.Component的类,我们可以定义一个函数,这个函数只包含一个 render 方法,接收 props作为参数,然后返回需要渲染的元素,并且不包含 state。
// 下面函数替换Square 类组件
function Square(props: any) {
return (
<button className="square" onClick={props.onSquareClick(props.index)}>
{props.value}
</button>
);
}
4、添加游戏的状态
如下,就是添加了一个下一步状态。
render的时候调用函数,直接把函数放在render里面。要想不再render,只需要不在改变state就好了,具体到这里,就是直接返回handleSquareClick
class Board extends React.Component<any,any> {
constructor(props: any) {
super(props);
this.state = {
squares: [
'O', null, 'X',
'X', 'X', 'O',
'O', null, null,
],
xIsNext: true}; // 这里多添加了一个状态
}
handleSquareClick(index: any) {
let squares = this.state.squares.slice(0);
if (calculateWinner(squares) || squares[i]) {
return; // 直接退出,不修改state了,就不会执行后面的render了
}
squares[index] = this.state.xIsNext ? 'X' : 'O'; // 这里做了一个判断
this.setState({squares:squares,xIsNext: !this.state.xIsNext,});
console.log(index);
}
renderSquare(i: any) {
return <Square index={i} value={this.state.squares[i]} onSquareClick={(index:any) => this.handleSquareClick(index)}/>;
}
render() {
const winner = calculateWinner(this.state.squares); // render(dom加载)的时候调用这个函数
let status;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
}
}
function calculateWinner(squares: any) {}
四、React优势
重新加载组件,依然是用key或者ref来控制(又好像不是。有可能key只是与性能有关)。key是React中的一个特殊的保留属性(以及ref,还有一个更高级的功能)。创建元素时,React提取key属性并将键直接存储在返回的元素上。即使key看起来像它所属props,key也无法使用引用this.props.key。React自动用于key确定要更新的组件
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>
);
});