React是一个JavaScript框架,用它可让咱们更便捷高效地进行前端开发。由于是入门,因此本文是以在html文件中引入js的方式进行练习的。若是想要直接建立并启动一个react项目(须要先安装好node和npm),可使用命令:javascript
npx create-react-app my-app
cd my-app
npm start
复制代码
官方文档地址:React。由于是React入门,因此本文不涉及Redux和React Router。css
首先须要准备ES6知识,尤为是class。(熟悉ES6的话能够跳过这部分)数组解构,对象解构等也须要了解。ES6的学习能够参考ES6入门。在此只简单说一下类相关的内容:html
// let container = new Container(100); // 类的声明不会提高,在此处使用Container会报错ReferenceError
// 类的声明
class Container {
// 构造函数constructor用于建立和初始化Container的实例对象
constructor ( totalCapacity ) {
this.totalCapacity = totalCapacity;
this.usedCapacity = 0;
this.contained = [];
// class中的方法不会自动绑定到实例对象,因此须要咱们手动绑定
this.add = this.add.bind(this);
this.remove = this.remove.bind(this);
this.printContained = this.printContained.bind(this);
}
// 静态方法,调用方式Container.containerInfo(),静态方法会被子类继承
static containerInfo () {
console.log('这个是静态方法呀\n');
}
// add、remove、printContained是定义在Container的prototype上的,能够被子类继承
add (something, volume) { // 将某物加入容器
const containedItem = {
name: something,
volume
};
let used = this.usedCapacity + volume;
if (used > this.totalCapacity) {
console.log(`此容器不能放下体积为${volume}的物品`);
return;
}
this.contained.push(containedItem);
this.usedCapacity = used;
}
remove (something, volume) { // 将某物移出容器
let containedLen = this.contained.length;
if (containedLen === 0) return;
const index = this.contained.findIndex((item) => item.name === something);
if (index < 0) return;
const item = this.contained[index];
const diff = item.volume - volume;
switch (true) {
case diff === 0:
this.contained.splice(index, 1);
break;
case diff > 0:
this.contained[index] = {
name: item.name,
volume: diff
};
break;
case diff < 0:
console.log(`容器中的${something}体积不足${volume}\n`);
break;
}
}
printContained () {
this.contained.forEach((item) => {
const { name, volume } = item;
console.log(`${name} ${volume}`);
})
}
}
let container = new Container(100); // 使用class定义的类必须用new调用,直接执行Container()会报TypeError
container.add('苹果', 20);
container.printContained();
container.remove('苹果', 10);
container.printContained();
// 若是没有在constructor中手动给printContained绑定this,那么执行如下两行代码就会报错
// 由于printContained执行时的this是undefined,不是container(Container的实例)。
// const { printContained } = container;
// printContained();
// 静态方法的调用方式
// Container.containerInfo();
// 类的继承
class Box extends Container { // Box会继承Container原型链上的方法
constructor (totalCapacity, material) {
// super方法调用父类的构造函数,将父类属性添加到子类上。至关于Container.constructor.call(this, 参数1, 参数2...);
super(totalCapacity);
this.material = material; // Box类的属性
this.printContained = this.printContained.bind(this);
}
printContained () {
console.log(`箱子是${this.material}制的`);
console.log('箱子中包含:');
super.printContained(); // 可使用super对象使用父类的方法,函数执行的过程当中this是指向Box实例的
}
}
let box = new Box(100, '木头');
box.add('芒果', 20);
box.add('西瓜', 10);
box.add('荔枝', 30);
box.printContained();
box.remove('芒果', 10);
box.printContained();
复制代码
由于这个🌰主要是为了说明类,因此基本上只有类相关的注释。前端
JSX是在React中使用的一种语法,如下代码对JSX进行了简单的说明:java
let test = () => 1;
let divStyle = { // 注意这里不是直接写的style样式,而是一个样式对象,样式属性采用的是驼峰的命名方式
backgroundColor: '#cfefe7',
fontSize: '15px',
textDecoration: 'underline'
};
let element = (
<div> {/* ()里面只能有一个元素 */} <h1>JSX</h1> <div style={divStyle} > {/* 这是内联样式的添加方法 */} <p>这就是使用JSX语法建立的一个“元素”,它既不是HTML也不是字符串。</p> <p>能够经过{}包含任何JS表达式。好比:{test() + 1}。而且JSX自己也是表达式。</p> { test() > 0 && <p>当test()的值大于0的时候会展现这部分的内容</p> } <p>里面不光能使用原生元素好比div等,还能包含在React中的自定义组件。</p> <p className="nothing">JSX语法中元素的属性采用驼峰的命名方式。</p> </div> </div>
);
复制代码
更多JSX相关内容,请查看官方文档:JSX。node
其实使用ES6和JSX在React中不是必须的,官方提供了相应的用法:不使用ES6、不使用JSX。本文仍是会在React中用ES6和JSX,由于使用ES6和JSX的代码更加简洁和直观。react
使用开发者工具可以方便查看组件以及组件的props,state。谷歌浏览器React开发者工具地址:React Developer Tools。 git
我在html中直接引入js文件,而后在浏览器中打开html文件的时候报跨域错误了:es6
Access to script at 'file:///Users/.../practice/00.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.github
因此我起了一个简单的node服务,在本地服务器上打开html件。
首先建立文件夹practice
,在文件夹中建立server.js
、practice.html
、practice.js
、practice.css
文件。
server.js
用Node.js建立了一个简单的本地服务器,文件内容以下:
const http = require('http');
const fs = require('fs');
const url = require('url');
const server = http.createServer(function (req, res) {
let pathname = url.parse(req.url).pathname;
fs.readFile(pathname.substr(1), function(err, data){
if (err) {
res.writeHead(404, {'Content-Type': 'text/html'});
} else {
const index = pathname.lastIndexOf('.');
const suffix = pathname.slice(index + 1);
res.writeHead(200, {'Content-Type': `text/${suffix}`});
res.write(data.toString());
}
res.end();
});
});
server.listen(8080, function () {
console.log(`server is running at http://127.0.0.1:8080`);
});
复制代码
practice.html
文件内容以下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" type="text/css" href="practice.css" />
</head>
<body>
<div id="root"></div>
<!-- 这里引用了react和react-dom以及babel的压缩版 -->
<script src="https://unpkg.com/react@16/umd/react.production.min.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel" src="practice.js"></script>
</body>
</html>
复制代码
practice.js
中内容以下:
// ReactDOM.render方法将React元素渲染到一个DOM元素中
ReactDOM.render(
<h1>React学习</h1>,
document.getElementById('root')
);
复制代码
pracitice.css
中内容以下:
#root h1 {
color: green;
}
复制代码
在practice目录下执行命令node server
启动服务。而后访问地址http://127.0.0.1:8080/practice.html
就能看到页面中的绿色的"React学习"这几个字。
元素是构成 React 应用的最小砖块。
React
元素和咱们已经熟悉的html
元素的元素名是同样的,例如div
,p
等,可是React
元素是一个普通的JavaScript
对象,而html
元素是真实的DOM
节点。二者的使用方式也不一样,React
元素的属性名称以及事件绑定都是使用驼峰的命名方式,React
元素还能在{}
中使用任何表达式,React
表单元素的使用也与html
表单元素不一样。
React
元素:
<div className="card" onClick={test} style={ {color: 'green', fontSize: '12px'} }>点击</div>
<input type="text" value={this.state.inputValue} onChange={this.handleChange} />
复制代码
在以上代码中,this.state.inputValue
的值就是输入框的值,当设置state
中的inputValue
值的时候,输入框的值也会改变。state
是组件中存放数据的地方,在下文的组件部分会进行说明。
想要将一个 React 元素渲染到根 DOM 节点中,只需把它们一块儿传入
ReactDOM.render()
。
例如上文中的这段代码:
// ReactDOM.render方法将React元素渲染到一个DOM元素中
ReactDOM.render(
<h1>React学习</h1>, // 这是React元素,是一个普通的对象
document.getElementById('root') // 这是一个DOM象
);
复制代码
html
元素:
<div class="card" onclick="test()" style="color: green; font-size: 12px;">点击</div>
<input type="text" name="userName" value="RenMo" onchange="handleChange(this.value)" />
复制代码
组件容许你将 UI 拆分为独立可复用的代码片断,并对每一个片断进行独立构思。
props
获取组件的属性。state
放置组件的数据,经过setState
设置组件的数据。将上文建立的practice.js
文件的内容替换以下:
function ShowText(props) {
const { style, content } = props;
return <p style={style}>{content}</p>;
}
const element = <ShowText content="React学习" style={ {color: 'green', fontSize: '30px'} }/>; ReactDOM.render( element, document.getElementById('root') ); 复制代码
打开http://127.0.0.1:8080/practice.html
,就能看见绿色的“React学习”几个字。
ShowText
是一个经过函数方式建立的组件,将它渲染到页面中一样须要使用ReactDOM.render
函数。函数的参数props中包含了所定义的组件的属性。组件的props
是只读的。
函数定义的组件,若是内容有须要改变的,那么必须从新调用一遍ReactDOM.render
,好比要改变content
的内容,就必须定义一个新的元素并调用ReactDOM.render
进行渲染:
const element = <ShowText content="React学习测试测试" style={ {color: 'green', fontSize: '30px'} }/>; ReactDOM.render( element, document.getElementById('root') ); 复制代码
从官方文档中了解到,要在不使用class
的状况下使用React
的state
等特性,须要使用Hook
。Hook
会在下文中提到。
将上文建立的practice.js
文件的内容替换以下(一个简单的待办清单的例子):
class TodoItem extends React.Component {
constructor (props) {
super(props);
this.state = {
complete: false
};
// 组件中定义的方法必须手动绑定this
this.changeTodoState = this.changeTodoState.bind(this);
}
componentDidMount() { // 生命周期方法 ------组件第一次渲染到DOM中会触发
this.timer = null; // 为了展现任务选中的效果,设置一个timer
}
componentDidUpdate () { // 生命周期方法 ------组件更新的时候会触发
console.log('组件更新了');
}
componentWillUnmount() { // 生命周期方法 ------组件从DOM中删除的时候会触发
clearTimeout(this.timer);
}
// 若是在输入框的点击事件中既要传入参数,又要使用event,那么把event放在参数的最后一个位置便可
changeTodoState (content, event) {
let value = event.target.value;
this.timer = setTimeout(() => {
if (value) {
// 经过setState改变状态的值
this.setState({
complete: true
});
// setCompleteItems是从父组件传过来的方法,调用这个方法能将子组件中的内容传入父组件中
// 也就是待办清单的某一项完成后,将该清单的content传入父组件中
this.props.setCompleteItems(content);
}
}, 200);
}
render () {
let complete = this.state.complete;
let content = this.props.content;
// 当complete完成的时候,返回的内容就是false,而不是组件的内容,因此清单的某一项就会隐藏
return (
(!complete) &&
<li className="todo-item" key={content.toString()}>
{/* 若是不须要绑定参数,在{}中直接写this.changeTodoState就行,若是要传入参数,则须要使用bind方法 */}
<input type="checkbox" onChange={this.changeTodoState.bind(this, content)}/>
<span>{content}</span>
</li>
);
}
}
class TodoList extends React.Component {
constructor (props) {
super(props);
this.state = {
completeItems: []
};
this.setCompleteItems = this.setCompleteItems.bind(this);
}
// 这个方法是为了将子组件中的内容传到父组件中,item就是从子组件传过来的内容
setCompleteItems (item) {
let completeItems = this.state.completeItems;
completeItems.push(item);
this.setState({
completeItems
});
}
render () {
let list = this.props.list;
let todoList = list.map((item) => <TodoItem content={item} setCompleteItems={this.setCompleteItems}/>);
return (
<div>
<ul className="todo-list">
{todoList}
</ul>
<p>已完成: {this.state.completeItems.join(',')}</p>
</div>
)
}
}
let list = ['洗衣服', '买水果', '追剧'];
const todoList = <TodoList list={list} />;
ReactDOM.render(
todoList,
document.getElementById('root')
);
复制代码
其中props
是组件的属性,是不可变的。state
是组件中的数据,能够经过setState
进行改变。
定义的事件须要在constructor
中手动绑定this
,不然就不能在组件中使用。
继承自React.Component
的类ShowText
中包含一个render
方法,每次组件更新render
方法都会被调用,返回的内容能够查看render方法官网地址,上面的练习例子中,返回的内容是一个React
元素。
componentDidMount
、componentWillUnmount
、componentDidUpdate
是经常使用的React
的生命周期函数,分别在React
组件的挂载、卸载、更新时被调用。还有其余的生命周期钩子函数:详情可看React生命周期。
若是要从子组件中改变父组件的内容,能够像上面那个例子中同样,在父组件中定义一个方法,上例的方法是setCompleteItems
,将其做为某属性的值传入子组件中,在子组件中经过this.props
获取到父组件传来的方法,再根据须要作出相应的修改。
经过上文能够了解到若是要使用state
和setState
等,就必须用类的方式定义组件,这样会致使组件的状态逻辑等有冗余而且可能比较复杂,使用hook
就是为了提供更直接简单的方法来进行开发。hook
能在不使用class
的状况下使用React
。React
从16.8.0版本开始支持hook
。
接下来就使用hook
的方法来实现上文中的待办清单的例子,仍是将practice.js
的内容替换以下:
// 在项目中可使用 import { useState } from 'react'; 引入useState
// 由于在浏览器中会将import转换为require,因而会报错require is not defined,因此使用下面这个句子引入useState
let useState = React.useState;
let useEffect = React.useEffect;
function TodoItem (props) {
const { content, setCompleteItems } = props;
// 声明一个名为complete的state变量,能够经过setComplete改变变量的值,complete的初始值为false。
const [ complete, setComplete ] = useState(false);
let timer = null; // 定时器
// useEffect的参数为一个函数,这个函数在组件挂载和更新时都会执行
useEffect(() => {
// 在useEffect中返回一个函数,这个函数会在组件清除的时候执行
return () => {
clearTimeout(timer)
}
});
function changeTodoState (content, event) {
let value = event.target.value;
timer = setTimeout(() => {
if (value) {
// 经过setComplete改变complete的值
setComplete(true);
setCompleteItems(content); // 这里本来是this.props.setCompleteItems(content);
}
}, 200);
}
return (
(!complete) &&
<li class="todo-item" key={content.toString()}>
{/* 若是不须要绑定参数,在{}中直接写this.changeTodoState就行,若是要传入参数,则须要使用bind方法 */}
<input type="checkbox" onChange={changeTodoState.bind(this, content)}/>
<span>{content}</span>
</li>
);
}
function TodoList (props) {
const { list } = props;
// 这里一开始定的completeItems的数据格式是数组,不止为什么使用setCompleteItemsState的时候,completeItems的值也变了,可是组件中的completeItems仍然为空
// 因此就换成字符串的格式了
const [ completeItems, setCompleteItemsState ] = useState('');
const setCompleteItems = (completedItem) => {
// 使用useState设置completeItems的值为数组没有生效,因而换成使用字符串,还须要手动组合一下字符串
let completeItemsText = completeItems;
completeItemsText = completeItems + (completeItemsText ? ',' + completedItem : completedItem);
setCompleteItemsState(completeItemsText);
};
let todoList = list.map((item) => <TodoItem content={item} setCompleteItems={setCompleteItems}/>);
return (
<div>
<ul class="todo-list">
{todoList}
</ul>
<p>已完成: {completeItems}</p>
</div>
)
}
let list = ['洗衣服', '买水果', '追剧'];
const todoList = <TodoList list={list} />;
ReactDOM.render(
todoList,
document.getElementById('root')
);
复制代码
能够看见使用hook
以后,就不用像在类中那样,须要使用this.state.varName
,直接使用varName
就能够了。
(!complete) &&
<li class="todo-item" key={content.toString()}> ... 复制代码
state hook
用于处理数据,对于effect hook
,直接引用官方文档的说明:
useEffect
就是一个 Effect Hook,给函数组件增长了操做反作用的能力。它跟 class 组件中的componentDidMount
、componentDidUpdate
和componentWillUnmount
具备相同的用途,只不过被合并成了一个 API。
在使用useEffect
的时候,若是须要在组件卸载的时候进行一些处理,那么在useEffect
返回一个函数,在清除effect
的时候,就会执行返回的函数的内容。好比如下代码,在组件卸载的时候,会将文档的标题改成''测试''。(这部分有点疑惑,虽然能够看见文档的标题在页面卸载前是没有变化的,可是在return
的函数中使用console.log
打印内容是可以打印出来的)。
useEffect(() => {
// 更新文档的title
document.title = '123';
return () => {
document.title = `测试`;
}
});
复制代码
useState
、useEffect
和useContext
是几个基础的hook
,也能够自定义hook
。
代码练习的最后结果是这样的:源码地址,使用DownGit可以拿到git仓库中某文件夹的代码。
上文中有提到,在React
入门的过程当中有两处疑问,可能须要进一步了解React
以后才能找到其缘由,这篇React
入门就写到这里了。
后天就是中华人民共和国成立70周年的日子,提早祝贺祖国母亲生日快乐ヾ(≧▽≦*)o o(*≧▽≦)ツ。