通过todoList实例快速react16入门(vue基础更容易理解)

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);
    }
}

复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值