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 react2npm start 复制代码
缺点:虽然利用官方脚手架很全面,但是对于修改webpack非常不方便,所以考虑使用第二种方法
方法二:generator-react-webpack
- 安装 安装还是在命令行用npm进行安装,不过在全局安装generator-react-webpack之前,你可以先安装yeoman。命令如下:
npm install -g yonpm install -g generator-react-webpack复制代码
- 创建目录 我们先用命令自行创建一个文件:new-react-demo
mkdir new-react-demo // 创建一个new-react-demo文件夹cd new-react-demoyo 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
React 复制代码
App.js
在js文件写html就是JSX语法,下面的return包裹的html就是JSX,使用JSX就必须要在开头引入react
// 功能:负责显示页面的内容import React, { Component } from 'react';// 相当于 // import React from 'react'// const Component = React.Componentimport logo from './logo.svg'; // 引入logo的svg图片import './App.css'; // 引入App.css文件// react创建组件的方式// 通过继承react.Component来创建组件class App extends Component { render() { // 返回什么,App组件就返回什么 return (hello React); }}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// *表示JSX语法,所以要在开头引入react *// 将 组件挂载到root节点下ReactDOM.render( , 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 (// 同vue一样,组件的html部分,最外层必须要包一层标签,一般都是用div。 // 这里面就写的是html,但是,这是一个必须符合JSX语法的html) } // (3)方法部分(事件部分) handleClick() {...} // ...等等方法 handleInputChange{...}}复制代码
先说几个(render中)要注意的点⚠️
- 给html标签加class样式,要写成ClassName="样式名"
复制代码
- 给html标签添加onclick或者onchange这些原生js有的事件,第二个字母必须首字母大写,并且要用{}来绑定方法
提交复制代码
- 标签中的值(vue和react区别)
{ {vue的方式(双括号)}}{react的写法(单括号)}复制代码
- 绑定html的属性,也是要用{}赋值
复制代码
- vue的v-for的react写法
- {this.state.list.map((item, index) => { return (
- {item} ) })}
- 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 () } 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 })) }}复制代码{this.state.list.map((item, index) => { return (
- {item}
) })}
父组件和子组件的传递(todolist改造)
将上面todoList中列表的实现部分封装成子组件TodoItem,然后通过父子组件的通信方式来实现功能
- 父组件传递props给子组件
import TodoItem from './TodoItem'; // 引入子组件...export default class TodoList extends Component { ... ...
- {this.state.list.map((item, index) => { return (
- 子组件接受,使用父组件传递的方法和参数
通过this.props.xxx
来使用父组件传递来的属性和方法
//TodoItem.jsimport 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 ({/* this.props.content是父组件传递来的值 */} {this.props.content}) } handleClick() { // 如果要修改父组件的data,则调用父组件的方法进行操作 // 但是在父组件要将this一并传递过来,才可以使用this this.props.handleDeleteItem(this.props.index); }}复制代码
优化成标准代码!⚠️
- 通过es6的解耦简化-子组件接收父组件的props
render() { const { content } = this.props; // 转换 return ({/* 只需要使用content就相当于之前的this.props.content */} {content})}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);}{this.props.content}// ❌避免下面的写法{this.props.content}复制代码
- JSX中体积不要太大
render() { return ( ...❌ul标签中写了一大堆的js的语句,应该剥离出来 ...
- {this.state.list.map((item, index) => { return (
- {item} ) })}
- {this.getTodoItem()}
- 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文件中我们可以看到如下语句:
// 将组件挂载到root节点下ReactDOM.render( , document.getElementById('root'));复制代码
目前框架的初始状态的入口文件index.js,是将App组件挂载到index.html中root这个dom上,也就是说,假设我们在index.html的root的dom节点后面写一个新的dom节点rootNew,这样的话,我们可以在里面创建另一个框架,然后仅仅是操作rootNew:
// 将组件挂载到root节点下ReactDOM.render( , 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 () } getTodoItem() { return this.state.list.map((item, index) => { return ({this.getTodoItem()}
< TodoItem content={item} index={index} handleDeleteItem={this.handleDelete} />) }) } 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 ({content}) } handleClick() { const { handleDeleteItem, index } = this.props; handleDeleteItem(index); }}复制代码