写在开头:
感觉社会变化很快,我记得从我第一次学习css
js
jquery
的时候,我都一直希望赶紧学习后端,因为觉得后端工资高,一直忽视前端,到现在才猛然发现,前端要解决的问题太多,前端越来越有竞争力,自己仿佛在不知不觉中被甩远了,后来从java转到了ruby后端,才意识到全栈才是自己真正想要达到的一个阶段,同事在离职前给我一个英文版的项目视频,因为现在的英文能力有限,所以购买了个中文版的项目,想利用休息时间,好好能系统过一遍,也想着记下笔记,利于自己复习,也方便他人。最后有地址,有完成之后的项目,是用react实现的简书项目
一:react开发环境准备
1.安装node
LTS比较稳定的版本 current最新的版本
安装完成后
node -v
npm -v
输出成功,说明node就安装成功了。
2.创建react脚手架
npm install -g create-react-app
注意:如果出现Error: EACCES: permission denied, access '
代表没有权限,加上sudo
命令即可。
create-react-app my-app
create-react-app helloWorld
Could not create a project called "helloWorld" because of npm naming restrictions:
* name can no longer contain capital letters
这里会自动帮你去构建react的项目工程。比如去安装一些react所需要的依赖包。
Success! Created helloworld at /Users/sai/Desktop/helloworld
Inside that directory, you can run several commands:
yarn start
Starts the development server.
yarn build
Bundles the app into static files for production.
yarn test
Starts the test runner.
yarn eject
Removes this tool and copies build dependencies, configuration files
and scripts into the app directory. If you do this, you can’t go back!
We suggest that you begin by typing:
cd helloworld
yarn start
cd helloworld
npm run start
脚手架工具帮我们分析源代码,启动一个服务器把源代码运行起来,看到的页面就是代码工程生成的页面
二:工程目录简介
通过create-app脚手架生成的主要目录介绍
-
node_modules
:是我们项目所依赖的第三方的包 -
public
-
favicon.ico
:图标 -
index.html
:当前显示的页面
-
-
yarn.lock
:项目的依赖包、版本号 不要动-
readme.md
:说明文件 -
package.json
: node包文件,可以把你的项目变成一个包
-
// package.json
{
"name": "helloworld", #名字
"version": "0.1.0", #版本
"private": true, #私有
"dependencies": { #依赖的包
"react": "^16.4.2",
"react-dom": "^16.4.2",
"react-scripts": "1.1.5"
},
"scripts": { #指令
"start": "react-scripts start", #之所以在命令行中可以输入npm run start启动,是因为在这里配置了 它去通过react-scripts工具去启动服务器
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
gitignore
:用过用git进行管理,你不想传递到git仓库,可以放到这里。
-
src
-
index.js
:
-
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import.. from
是es6提供的模块引入的语法。
这里所引入的React
ReactDOM
对应的package.json
中dependencies
存在的库。
// PWA progressive web application
// 通过写网页的形式来写手机app应用
// 写一个网页,上线到https服务器上,当用户断网的时候,再访问的时候还是能看到之前已经加载好的页面
import registerServiceWorker from './registerServiceWorker';
三:react中的组件
一个页面如果很复杂,我们写起来很麻烦。但是如果写成一个个可拆分的组件的时候,我们就好管理的多。而且组件的形式也可重用性强。这也就是前端同学经常说的 前端组件化 的含义。
这里再针对于本章说下最基础的jsx语法
- 我们在之前的js返回一般写的是
return <div>hello, jianshu</div>
,而在jsx中是不加但双引号的。另外,在jsx中,不单单可以引入普通的标签,还可以引入组件。 - 在jsx引入组件的时候,比如引入
<App />
组件,这里的组件首字母必须要大写,不能是<app />
,因为这种jsx语法是不支持的,所以要用jsx语法,首字母必须大写。所以,大写字母开头的话,一般是组件。
从脚手架生成的app.js
我们进行分析
// app.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component { // 用es6的语法写了继承
render() {
return (
<div>
hello, jianshu.
</div>
);
}
}
export default App; //导出操作,正好index.js引入了,所以显示出来了
app.js
中render
下的<div>
也是jsx
语法,所以必须引入React
,引入之后,才能编译成功。
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
ReactDOM
是第三方的模块,它有一个方法是render
,这个方法帮我们把一个组件挂在dom节点上。这里挂在在root下。
<div id="root">
...
</div>
<App />
是jsx
语法,需要引入对应的react。
再整体介绍一遍:
index.js
是程序入口,引入React
ReactDOM
,,接着引入了一个组件,叫APP
,用reactdom把这个组件显示在root
节点。
因为这里用了jsx语法,所以需要引入import React, { Component } from 'react';
,然后再看app.js
,它就是个名字叫App
的组件,在这个组件中返回了一些内容,因为它是一个react组件,所以必须引入import React, { Component } from 'react';
,这个App
必须继承这个React.Component
才可以生成这个组件。
1.1 初探
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import TodoList from './TodoList';
ReactDOM.render(<TodoList />, document.getElementById('root'));
todoList.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class TodoList extends Component {
render() {
return (
<div>TodoList</div>
<ul>
<li>学习</li>
<li>写作</li>
</ul>
)
}
}
export default TodoList; //把自身导出,外部才可以引用。
会报下面的错
./src/TodoList.jsSyntax error: Adjacent JSX elements must be wrapped in an enclosing tag (8:6)
这是因为在jsx的语法中,一个组件render函数返回的内容外层必须要有一个包裹元素。现在有两个,一个<ul>
,一个 <div>
,在外层加个<div>
即可。
如果不想套用一个div怎么办?
在react16的语法中,react提供了一个叫Fragiment
的占位符。
return (
<Fragment>
<div>TodoList</div>
<ul>
<li>学习</li>
<li>写作</li>
</ul>
</Fragment>
)
2.2 React中的响应式设计思想和事件绑定
react
英文就是响应的意思,react
跟英文名一样的含义:数据响应,页面发生变化。
react更改state的状态的话不能通过以下来进行修改
this.state.inputValue = e.target.value //e.target会获取dom节点,再.value就会获取value值
而是用react提供的方法setState
import React, { Component, Fragment } from 'react';
import ReactDOM from 'react-dom';
class TodoList extends Component {
constructor(props) {
super(props)
this.state = {
inputValue: '',
list: []
}
}
handleInputChange (e) {
this.setState({
inputValue: e.target.value
})
}
render() {
return (
<Fragment>
<div>
<input value={this.state.inputValue} onChange={this.handleInputChange.bind(this)} />
<button>提交</button>
</div>
<ul>
<li>学习</li>
<li>写作</li>
</ul>
</Fragment>
)
}
}
export default TodoList; //把自身导出,外部才可以引用。
介绍:
页面刚开始初始化的时候,会有两个初始值inputValue
和list
值,render
会渲染值为this.state.inputValue
会初始化值,然后输入内容onChange
会执行,e.target.value
会获取值,然后改变inputValue
的值,inputValue
的值发生改变了,页面input
标签里的值发生了改变,那么页面也会发生改变。
注意:
js表达式用{}
时间绑定用bind(this)
对函数的作用域进行变更,改变this
的指向
setState
来对数据项就行改变
2.3 实现todoList新增删除功能
增加功能
list: ['study1', 'study2']
...
<ul>
{
this.state.list.map((item, index) => {
return <li>{item}</li>
})
}
</ul>
这里就会有生成两个li
,也就是list中的值。所以我们实现增加功能,不用关注dom,只需要关注数据就行了。要做的就是把inputValue
的值放在 list
中即可。这样数组中有内容,页面就会接着变了。
handleBtnClick() {
this.setState({
list: [...this.state.list, this.state.inputValue]
})
}
...
是es6的展开运算符,...this.state.list
指的是初始化是时候list所定义的值。把以前的数组展开生成全新的数组,所以外面套个[]
会发现页面报Warning: Each child in an array or iterator should have a unique "key" prop.
<ul>
{
this.state.list.map((item, index) => {
return <li key={index}>{item}</li>
})
}
</ul>
map循环需要加上key,key必须保证唯一,要不会报警告,这里用index
删除功能
点击item
的时候获取下标值。把index
放在bind
函数中,传递给delete方法,用splice
方法删除即可。
import React, { Component, Fragment } from 'react';
import ReactDOM from 'react-dom';
class TodoList extends Component {
constructor(props) {
super(props)
this.state = {
inputValue: '',
list: []
}
}
handleInputChange (e) {
this.setState({
inputValue: e.target.value
})
}
handleBtnClick() {
this.setState({
list: [...this.state.list, this.state.inputValue],
inputValue: ''
})
}
// 删除
handleItemDelete (index) {
// immutable
// state 不允许我们做任何改变
const list = [...this.state.list]
list.splice(index, 1)
this.setState({
list: list
})
}
render() {
return (
<Fragment>
<div>
<input value={this.state.inputValue} onChange={this.handleInputChange.bind(this)} />
<button onClick={this.handleBtnClick.bind(this)}>提交</button>
</div>
<ul>
{
this.state.list.map((item, index) => {
return (<li
key={index}
onClick={this.handleItemDelete.bind(this, index)}>
{item}</li>
)
})
}
</ul>
</Fragment>
)
}
}
export default TodoList; //把自身导出,外部才可以引用。
一:jsx语法细节补充
- 定义css的类的时候,不能用
class
,要用className
来代替class
这个关键词,因为我们用class
已经声明类了,这样会重复定义。 - 注释的话用
{/*我是注释*/}
- 点击
label
获取input
的光标,可以在input上定义一个id,比如这个id是insertArea,那么在label中加htmlFor
。代替之前的for
关键字。 - 在
import React, { Component }
,这里的{ Component}
是结构赋值。
二:拆分组件与组件之间的传值
组件化思维
react是树形的结构,这里todoList是大组件,todoItem是todoList下的小组件。
父组件向字组件传递数据用属性才进行传递,通过标签属性传递(属性和方法)过去之后,子组件用this.props.*
来进行接收。
子组件如何调用父组件的方法,并改变里面的数据?把父组件的方法传给子组件即可。用this.props.*
,父组件如果传递方法,需要做一次绑定。
要不然会出现下面的错误:
TypeError: Cannot read property 'list' of undefined
三:优化todoList
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import TodoList from './TodoList';
ReactDOM.render(<TodoList />, document.getElementById('root'));
//TodoList.js
import React, { Component, Fragment } from 'react';
import TodoItem from './TodoItem';
class TodoList extends Component {
constructor(props) {
super(props)
this.state = {
inputValue: '',
list: []
}
this.handleInputChange = this.handleInputChange.bind(this);
this.handleBtnClick = this.handleBtnClick.bind(this);
this.handleItemDelete = this.handleItemDelete.bind(this);
}
handleInputChange (e) {
const value = e.target.value
this.setState(() => ({
inputValue: value
}))
// old
// this.setState({
// inputValue: e.target.value
// })
}
handleBtnClick() {
//setState有一个参数可以接收以前的数据
this.setState((prevState) => ({
list: [...prevState.list, prevState.inputValue],
inputValue: ''
}))
// old
// this.setState({
// list: [...this.state.list, this.state.inputValue],
// inputValue: ''
// })
}
handleItemDelete (index) {
// immutable
// state 不允许我们做任何改变
this.setState((prevState) => {
const list = [...prevState.list]
list.splice(index, 1);
return {list}
});
}
getTodoItem () {
return this.state.list.map((item, index) => {
return (
<TodoItem
key={index}
content={item}
index={index}
deleteItem={this.handleItemDelete}
/>
)
})
}
render() {
return (
<Fragment>
<div>
<input value={this.state.inputValue} onChange={this.handleInputChange} />
<button onClick={this.handleBtnClick}>提交</button>
</div>
<ul>
{this.getTodoItem()}
</ul>
</Fragment>
)
}
}
export default TodoList; //把自身导出,外部才可以引用。
//TodoItem.js
import React, { Component } from 'react';
class TodoItem extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
const { deleteItem ,index } = this.props
deleteItem(index);
}
render() {
const { content } = this.props
return (
<div onClick={this.handleClick}>
{content}
</div>
)
}
}
export default TodoItem;
四:围绕react衍生出的思考
- 直接操作dom的开发方式是
命令式开发
的方式,比如我们熟悉的jquery
- 而react是
声明式开发
可以与其他框架并存
组件式开发
- react是单向数据流,只允许父组件向子组件传递数据,子组件绝对不能修改父组件传递的数据,而必须要删除的话是子组件调用父组件的方法,然后进行删除,这里实际上也是调用对父组件进行操作,这样只需要维护父组件即可,维护起来比较容易
- react是一个视图层框架,小型项目即可。而大型的需要依赖
Flux
redux
等这样的数据层框架 - 函数式编程,都是一个一个的函数组成,方便于测试,给前端的自动化测试带来很大的便捷性。
网友评论