author: thomaszhou
我以前是学习vue的,没有接触过react,但是现在是react16,要学肯定是最新的啊,所以我们这里也是学习react16,既然vue和react都是mvvm框架,学下来,我发现其实很多东西虽然有区别,但是很熟悉,react语法熟悉起来也是非常的快,对于有vue基础的童鞋可以在边学习react语法的时候,一遍去类比自己的vue的语法,这样会很快的上手,好了,开始正题
构建项目
react那些构建工具都是热加载(不是自动刷新浏览器,只是自动更新修改的部分)
方法一:create-react-app(用于快速构建开发环境的脚手架工具)
- 安装需要在命令行中进行,在安装create-react-app前,你需要安装node。然后在命令行中输入下面的命令:
Liunx和Mac电脑下:(这里的 -g 是全局安装的意思。)
sudo npm install -g create-react-app
复制代码
- 创建React项目(项目名为react2)
- 小坑:文件名不要使用大写,这样作只要是为了严谨性,因为在Linux下是严格区分大小写的
create-react-app react2
复制代码
- 启动服务
cd react2
npm start
复制代码
缺点:虽然利用官方脚手架很全面,但是对于修改webpack非常不方便,所以考虑使用第二种方法
方法二:generator-react-webpack
- 安装 安装还是在命令行用npm进行安装,不过在全局安装generator-react-webpack之前,你可以先安装yeoman。命令如下:
npm install -g yo
npm install -g generator-react-webpack
复制代码
- 创建目录 我们先用命令自行创建一个文件:new-react-demo
mkdir new-react-demo // 创建一个new-react-demo文件夹
cd new-react-demo
yo react-webpack // 用生成器生成我们的项目目录
复制代码
- 运行
npm start
复制代码
优点
优点介绍:
1、基于webpack构建,可以很容易的配置自己需要的webpack。
2、支持ES6,集成了Babel-Loader。
3、支持不同风格的CSS(sass,less,stylus)。
4、支持PostCSS转换样式。
5、集成了esLint功能。
6、可以轻松配置单元测试,比如Karma和Mocha
缺点:就是要依靠yeoman来生成。
复制代码
路由
- react-router:是基本的router包,里边函的内容较多,但是在网页开发中有很多用不到,现在的市面上的课程讲的基本都是这个包的教程。
- react-router-dom:随着react生态环境的壮大,后出现的包,这个包比react-router包轻巧了很多。
- 其实安装了react-router包就不用安装了react-router-dom包了,因为react-router-dom是react-router的子集
react-router 和 react-router-dom 二选一即可
npm install --save react-router react-router-dom
复制代码
通过react脚手架了解目录文件
通过脚手架创建的初始项目的目录如下(挑部分讲)
- pubilc
- index.html 页面的整体html模板
- src
- App.js 是一个组件,负责显示页面的内容
- App.css
- App.test.js 自动化测试文件
- index.js 入口文件,并且引入App.js并挂载到index.html中的root节点中
- index.css
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<title>React</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<!--重点: root节点-->
<div id="root"></div>
</body>
</html>
复制代码
App.js
在js文件写html就是JSX语法,下面的return包裹的html就是JSX,使用JSX就必须要在开头引入react
// 功能:负责显示页面的内容
import React, { Component } from 'react';
// 相当于
// import React from 'react'
// const Component = React.Component
import logo from './logo.svg'; // 引入logo的svg图片
import './App.css'; // 引入App.css文件
// react创建组件的方式
// 通过继承react.Component来创建组件
class App extends Component {
render() { // 返回什么,App组件就返回什么
return (
<div className="App">
hello React
</div>
);
}
}
export default App;
复制代码
index.js
// 入口文件
// 功能:引入APP文件,渲染到页面上
import React from 'react'; // 引入react包
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker'; // 支持PWA
// * <App />表示JSX语法,所以要在开头引入react *
// 将<App/>组件挂载到root节点下
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker(); // 使PWA生效
复制代码
todolist快速了解React
我们这里就不讲究这么多了,就做一个简单的todolist,牵扯到父子组件的通信,以及一些常用的使用方法让我们快速了解react最基本的使用方法,后续的深入需要自己额外的学习了。
- 不同Vue的是,vue可以通过v-model来进行双向绑定,改变任何一方都可以自动牵动另一方变化,但是react不是,他是单向绑定,也就是说数据改变驱动视图层的改变(input就是比较典型的例子,后续会详解)
react创建组件的方式
import React, { Component } from 'react';
import TodoItem from './TodoItem';
import './style.css'
import './../index.css'
export default class TodoList extends Component {
// (1)定义数据
// constructor是最先优先被执行
constructor(props) {
super(props); // 调用父类函数方法,继承
// 组件的状态state
this.state = {
inputValue: '',
list: []
}
}
// (2)render部分(也是写JSX语法部分)
render() {
return (
<div>
// 同vue一样,组件的html部分,最外层必须要包一层标签,一般都是用div。
// 这里面就写的是html,但是,这是一个必须符合JSX语法的html
</div>
)
}
// (3)方法部分(事件部分)
handleClick() {...}
// ...等等方法
handleInputChange{...}
}
复制代码
先说几个(render中)要注意的点⚠️
- 给html标签加class样式,要写成ClassName="样式名"
<input className='ex1'>
复制代码
- 给html标签添加onclick或者onchange这些原生js有的事件,第二个字母必须首字母大写,并且要用{}来绑定方法
<!-- xxx.bind(this)是为了将this指向本组件(不是必须)-->
<a className="input-btn" onClick={this.handleClick.bind(this)}>提交</a>
复制代码
- 标签中的值(vue和react区别)
<div>{{vue的方式(双括号)}}</div>
<div>{react的写法(单括号)}<div>
复制代码
- 绑定html的属性,也是要用{}赋值
<!--input的value绑定到数据中的inputValue变量-->
<input className='input' value={this.state.inputValue} type="text" />
复制代码
- vue的v-for的react写法
<!--list是一个数组-->
<ul >
{this.state.list.map((item, index) => {
return (
<li key={index}
onClick={this.handleDelete.bind(this, index)}
>{item}</li>
)
})}
</ul>
复制代码
- react要修改数据层的值,不能像vue一样直接
this.inputValue="1"
,必须要用this.setState
handleDelete(index) {
// 不推荐直接在setState中直接修改数组的值(例如增加,删除)
// 遵循immutable特性,就是state不允许我们直接改变
const listTemp = [...this.state.list];
listTemp.splice(index, 1);
this.setState(() => ({
list: listTemp
}))
}
复制代码
todolist的源代码
首先我们要将数据inputValue和input标签的value属性进行绑定,但是react是单向绑定,也就是说,修改数据inputValue,input的value会跟着变化,但是我们在页面上是不能修改input的Value值,因为没有效果。。。
输入数组,添加list (思路):将inputValue和input的value进行绑定,然后再绑定onChange事件到handleInputChange()方法,使得当在页面修改input的value值时,会利用this.setState,将value值赋值给inputValue(相当于我们自己自主实现另一方向的绑定,vue是直接默认双向绑定的);点击按钮后,将inputValue 的值添加进list数组,然后将list数据渲染到下面的列表中
点击list数据,删除当前数据(思路):给下面列表中的每个数据都绑定click事件,点击列表数据,将index传递给函数handleDelete(index),删除list数组中对应的数据,就可以完成更新(但是要遵循immutable特性,即:不能直接修改state中的数据,而是通过建立一个copy,然后对copy进行修改,再利用setState()进行赋值)
import React, { Component } from 'react'
import TodoItem from './TodoItem';
import './style.css'
import './../index.css'
export default class TodoList extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: '',
list: []
}
}
render() {
return (
<div>
<nav className="mainNav">
<section className='inputFrame'>
<input className='input'
value={this.state.inputValue}
onChange={this.handleInputChange.bind(this)}
type="text" />
<a className="input-btn" onClick={this.handleClick.bind(this)}>提交</a>
</section>
</nav>
<ul >
{this.state.list.map((item, index) => {
return (
<li key={index}
onClick={this.handleDelete.bind(this, index)}
>{item}</li>
)
})}
</ul>
</div>
)
}
handleClick() {
this.setState(() => ({
list: [...this.state.list, this.state.inputValue],
inputValue: ""
}))
}
handleInputChange(e) {
// 因为setState是异步的,需要将e.target.value先保存起来,为什么?
const value = e.target.value;
this.setState(() => ({
inputValue: value
}))
}
handleDelete(index) {
// 不推荐直接在setState中直接修改数组的值(例如增加,删除)
// 遵循immutable特性,就是state不允许我们直接改变,好处是什么??
const listTemp = [...this.state.list];
listTemp.splice(index, 1);
this.setState(() => ({
list: listTemp
}))
}
}
复制代码
父组件和子组件的传递(todolist改造)
将上面todoList中列表的实现部分封装成子组件TodoItem,然后通过父子组件的通信方式来实现功能
- 父组件传递props给子组件
import TodoItem from './TodoItem'; // 引入子组件
...
export default class TodoList extends Component {
... ...
<ul >
{this.state.list.map((item, index) => {
return (
<div>
{/*传递值:父组件将item通过content属性传递给子组件 */}
{/*传递方法:父组件将方法handleDelete传递给子组件,但是要将this同时传递到子组件 */}
< TodoItem content={item} index={index}
handleDeleteItem={this.handleDelete.bind(this)} />
</div>
)
})}
</ul>
}
复制代码
- 子组件接受,使用父组件传递的方法和参数
通过this.props.xxx
来使用父组件传递来的属性和方法
//TodoItem.js
import React, { Component } from 'react'
export default class TodoItem extends Component {
constructor(props) {
super(props);
// 使得handleClick的this指向当前组件,不然默认是undefined
// 统一将方法的bind都写在constructor中
this.handleClick = this.handleClick.bind(this);
}
render() {
return (
<div onClick={this.handleClick}>
{/* this.props.content是父组件传递来的值 */}
{this.props.content}
</div>
)
}
handleClick() {
// 如果要修改父组件的data,则调用父组件的方法进行操作
// 但是在父组件要将this一并传递过来,才可以使用this
this.props.handleDeleteItem(this.props.index);
}
}
复制代码
优化成标准代码!⚠️
- 通过es6的解耦简化-子组件接收父组件的props
render() {
const { content } = this.props; // 转换
return (
<div onClick={this.handleClick}>
{/* 只需要使用content就相当于之前的this.props.content */}
{content}
</div>
)
}
handleClick() {
const { handleDeleteItem, index } = this.props;
handleDeleteItem(index);
// ❌替代 this.props.handleDeleteItem(this.props.index);
}
复制代码
- 统一将方法的bind都写在constructor中
constructor(props) {
super(props);
// 使得handleClick的this指向当前组件,不然默认是undefined
this.handleClick = this.handleClick.bind(this);
}
<div onClick={this.handleClick}>
{this.props.content}
</div>
// ❌避免下面的写法
<div onClick={this.handleClick.bind(this)}>
{this.props.content}
</div>
复制代码
- JSX中体积不要太大
render() {
return (
...❌ul标签中写了一大堆的js的语句,应该剥离出来
...
<ul >
{this.state.list.map((item, index) => {
return (
<li key={index}
onClick={this.handleDelete.bind(this, index)}
>{item}</li>
)
})}
</ul>
)
}
// 改进后
render() {
return (
... ...
<ul>
{this.getTodoItem()}
</ul>
)
}
// 将过于负责的js逻辑放入函数中
getTodoItem() {
// 然后记得return回来
return this.state.list.map((item, index) => {
return (
<div>
{/* 父组件将item通过content属性传递给子组件 */}
< TodoItem content={item} index={index} handleDeleteItem={this.handleDelete} />
</div>
)
})
}
复制代码
- setState可以接受一个参数prevState,表示修改数据之前那一次的数组的数值
// 旧版本
handleClick() {
this.setState(() => ({
list: [...this.state.list, this.state.inputValue],
inputValue: ""
}));
}
// 新版本
handleClick() {
this.setState((prevState) => ({
list: [...prevState.list, prevState.inputValue],
inputValue: ""
}));
}
复制代码
this.setState(() => ({}))
其实等价于this.setState(() => {return {} })
// 旧版
handleDelete(index) {
const listTemp = [...this.state.list];
listTemp.splice(index, 1);
this.setState(() => ({
list: listTemp
}));
}
// 新版
handleDelete(index) {
this.setState((prevState) => {
const listTemp = [...prevState.list];
listTemp.splice(index, 1);
return { list: listTemp }
});
}
复制代码
衍生的思考
声明式开发
直接操作dom那是命令式编程,我们每次要进行操作都要去获取dom节点,然后操作dom节点的一些属性或者值,但是MVVM框架(vue,react,angular)都是面向数据来编程,框架会根据数据来构建整个页面的dom,这会帮助我们减少大量dom操作
可以和其他框架并存
在index.js文件中我们可以看到如下语句:
// 将<App/>组件挂载到root节点下
ReactDOM.render(<App />, document.getElementById('root'));
复制代码
目前框架的初始状态的入口文件index.js,是将App组件挂载到index.html中root这个dom上,也就是说,假设我们在index.html的root的dom节点后面写一个新的dom节点rootNew,这样的话,我们可以在里面创建另一个框架,然后仅仅是操作rootNew:
// 将<App2/>组件挂载到root节点下
ReactDOM.render(<App2 />, document.getElementById('rootNew'));
复制代码
这样的话,我们不同的框架只需要考虑自己对应的那个dom节点就可以了,这样也就不会相互影响了,可以共存多个框架
组件化
单向数据流
react要求编程有个单向数据流的概念,父组件可以向子组件传递内容,子组件只能使用这个值,但是不能改变这个值,所以如果子组件要修改父组件的值,那就要通过使用父组件的方法函数来修改父组件的值
react是视图层的框架
只解决数据和页面渲染上的一些问题,打个比方,我们的父子层已经好多层了,那兄弟组件或者亲戚组件(类似你父亲的兄弟组件,或者爷爷组件的兄弟组件)要进行通信,我们就需要一层层往上传递,又要一层层向下传递props,这样中间链路上的组件都要参与到这个传递的事情中,就会造成巨大的麻烦,这个时候就需要一些数据层的框架进行配合
函数式编程
写代码中,其实都是在写一个个的函数,这对自动化测试会带来巨大的便利,他们只需要给一个个函数设置输入,输出就可以去判断函数的正确性
最后的代码
- TodoList.js
import React, { Component } from 'react'
import TodoItem from './TodoItem';
import './style.css'
import './../index.css'
export default class TodoList extends Component {
// 定义数据
constructor(props) {
super(props); // 调用父类函数方法,继承
this.handleClick = this.handleClick.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
this.handleDelete = this.handleDelete.bind(this);
// 组件的状态state
this.state = {
inputValue: '',
list: []
}
}
render() {
return (
<div>
<nav className="mainNav">
<section className='inputFrame'>
<input className='input'
value={this.state.inputValue}
onChange={this.handleInputChange}
type="text" />
<a className="input-btn" onClick={this.handleClick}>提交</a>
</section>
</nav>
<ul >
{this.getTodoItem()}
</ul>
</div>
)
}
getTodoItem() {
return this.state.list.map((item, index) => {
return (
<div key={index}>
< TodoItem content={item} index={index} handleDeleteItem={this.handleDelete} />
</div>
)
})
}
handleClick() {
this.setState((prevState) => ({
list: [...prevState.list, prevState.inputValue],
inputValue: ""
}))
}
handleInputChange(e) {
const value = e.target.value;
this.setState(() => ({
inputValue: value
}))
}
handleDelete(index) {
this.setState((prevState) => {
const listTemp = [...prevState.list];
listTemp.splice(index, 1);
return { list: listTemp }
});
}
}
复制代码
- TodoItem.js
import React, { Component } from 'react'
export default class TodoItem extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
render() {
const { content } = this.props;
return (
<div onClick={this.handleClick}>
{content}
</div>
)
}
handleClick() {
const { handleDeleteItem, index } = this.props;
handleDeleteItem(index);
}
}
复制代码