以下同样同样同样是我在复习学习React时,整理的笔记,可以参考学习
React官网:React 官方中文文档 – 用于构建用户界面的 JavaScript 库 (docschina.org)
这上面的教程就很不错,以下的主要内容都参考自这个官方教学文档
(示例代码放的比较多,目的是通过代码理解抽象的概念,另一方面同时也能积累一些代码经验)
在构建前端页面时,仅仅使用三剑客html、css、js的话,理论上可以写出各种页面,但是写起来非常复杂,比如js控制DOM时用原生的写法getElementById写起来很繁琐,可读性也不高,因此可以使用一些框架,来简化开发过程,我了解的主流框架有Vue、React,都可以简化开发过程且提升渲染效率。以下是关于React具体的相关知识。
React是一个用于构建用户界面的JavaScript库
React具有以上三个特点,且前端人员可以通过React Native来进行原生移动应用开发
声明式:只需要描述UI看起来是什么样子
组件化:把页面中的构成组件化
一次学习,随处编写:开发VR、开发web、开发移动端等等
但是学习React要额外学习一门JSX语言,它是一个JavaScript的语法扩展
安装React
npm i react react-dom
引入react和react-dom的两个js文件
<script src="./node_modules/react/umd/react.development.js">script>
<script src="./node_modules/react-dom/umd/react-dom.development.js">script>
如果要写JSX代码,还需要另外引入
初始化项目,命令: npx create-react-app my-app
启动项目(在项目的根目录执行),在项目根目录执行命令: npm start
(要注意版本问题)
const element = Hello, world!
;
这种语法就是JSX语法
JSX 可以生成 React “元素”,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。JSX 可能会使人联想到模版语言,但它具有 JavaScript 的全部功能。React 不强制要求使用 JSX,但是大多数人发现,在 JavaScript 代码中将 JSX 和 UI 放在一起时,会在视觉上有辅助作用。它还可以使 React 显示更多有用的错误和警告消息。
在JSX中嵌入表达式
在 JSX 语法中,你可以在大括号内放置任何有效的JavaScript表达式
function formatName(user) {
return user.firstName + ' ' + user.lastName;
}
const user = {
firstName: 'Harper',
lastName: 'Perez'
};
const element = (
Hello, {formatName(user)}!
);
ReactDOM.render(
element,
document.getElementById('root')
);
同时JSX也是一个表达式
也就是说,你可以在 if
语句和 for
循环的代码块中使用 JSX,将 JSX 赋值给变量,把 JSX 当作参数传入,以及从函数中返回 JSX:
function getGreeting(user) {
if (user) {
return Hello, {formatName(user)}!
;
}
return Hello, Stranger.
;
}
JSX可以使用大括号来在属性值中插入JavaScript表达式,比如:
const element = ;
这种方式等于把属性也改成了变量,根据其具体的值而更新渲染
JSX能包含很多子元素
const element = (
Hello!
Good to see you here.
);
相当于在JSX里面就能写html语句
元素是构成React应用的最小砖块
可以理解每一个const就是一个元素,描述了想在屏幕上看到的内容
需要将元素传入ReactDOM.render();
const element = Hello, world
;
ReactDOM.render(element, document.getElementById('root'));
在html中要有一个根DOM节点
<div id="root">div>
React元素是不可变的,如果想要更新就要创建全新的元素,将其渲染进ReactDOM.render()里面
比如一个计时器的例子:
function tick() {
const element = (
Hello, world!
It is {new Date().toLocaleTimeString()}.
);
ReactDOM.render(element, document.getElementById('root'));
}
setInterval(tick, 1000);
每隔一秒调用tick函数,更新了element,然后调用ReactDOM.render(),重新渲染
实现了动态更新
React DOM会将元素及其子元素与之前的状态进行比较,而只更新变化的部分来达到预期
这样就提高了渲染效率!!
组件:从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的React 元素。
组件允许将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。
这种思想将一整个UI拆分成一个一个可复用的小块,非常高效
组件名称必须以大写字母开头!!!
function Welcome(props) {
return Hello, {props.name}
;
}
这是一个简单的函数组件,接收props参数,返回react元素
class Welcome extends React.Component {
render() {
return Hello, {this.props.name}
;
}
}
这两个组件的效果是一样的
React元素可以是DOM标签、也可以是用户自定义的组件
const element = ;
const element = ;
当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。
在构建页面时,我们往往要把页面拆分成许多个组件,这些组件之间也存在一定联系,可以进行嵌套、组合
function Welcome(props) {
return Hello, {props.name}
;
}
function App() {
return (
);
}
ReactDOM.render(
,
document.getElementById('root')
);
这是一个多次渲染welcome组件的app组件
就是将组件拆分成更小的组件
有时候一个组件有很多的嵌套关系,难以维护,难以理解,而且难以复用
这时就可以将某些功能拆分出来,设计成独立的组件
比如这是一个组件实现的功能:
function Comment(props) {
return (
{props.author.name}
{props.text}
{formatDate(props.date)}
);
}
这是拆分后的完整代码:
function formatDate(date) {
return date.toLocaleDateString();
} //一个获取时间的函数
function Avatar(props) {
return (
);
} //Avatar组件,渲染图片
function UserInfo(props) {
return (
{props.user.name}
);
} //UserInfo组件,渲染用户信息
function Comment(props) {
return (
{props.text}
{formatDate(props.date)}
);
} //Comment组件,渲染整体架构
const comment = {
date: new Date(),
text: 'I hope you enjoy learning React!',
author: {
name: 'Hello Kitty',
avatarUrl: 'https://placekitten.com/g/64/64',
},
}; //jsx元素,用来储存信息
ReactDOM.render(
,
document.getElementById('root')
); //进行渲染
在多个组件嵌套时,我个人觉得属性很难有一个清晰的思路
把要用到的属性提前整理出来,在传递、调用的时候就不容易搞错
整体的思路也会更清晰一些
我们要尽量避免修改Props,如果要进行修改变化,要用到下面的State
在前面时钟的例子中,需要多次更新某一值,因此解决办法是多次调用ReactDOM.render()方法,但是还有一种更简单的方法,用类组件中的State
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
Hello, world!
It is {this.state.date.toLocaleTimeString()}.
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);
当State值变化时,会自动重新渲染变化的部分
注意
1.不能直接修改State的值,而是应该调用setState方法进行修改
2.State和Props的更新可能是异步的
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
3.setState更新会被合并,单独更新,系统自动合并
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
组件由第一次渲染到最后被销毁的生命周期
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(), //调用自身的方法要加this
1000
);
} //在组件第一次被渲染后执行
componentWillUnmount() {
clearInterval(this.timerID);
} //在组件被销毁时执行
tick() {
this.setState({
date: new Date()
});
} //修改State值
render() {
return (
Hello, world!
It is {this.state.date.toLocaleTimeString()}.
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);
State具有较好的封装性,外部无法直接访问,但是可以在组件内作为参数传递给子组件,且只能由父组件传给子组件
比如:
如果把一个以组件构成的树想象成一个 props 的数据瀑布的话,那么每一个组件的 state 就像是在任意一点上给瀑布增加额外的水源,但是它只能向下流动。
html与jsx在事件处理上有一些差别
//在html中的事件处理
//在jsx中的事件处理
在 React 中另一个不同点是不能通过返回 false
的方式阻止默认行为。必须显式的使用 preventDefault
。
//html
Click me
//jsx
function ActionLink() {
function handleClick(e) { //e是一个合成事件
e.preventDefault();
console.log('The link was clicked.');
}
return (
Click me
);
}
当定义一个类组件的时候,通常的做法是将事件处理函数声明为 class 中的方法。例如,下面的 Toggle
组件会渲染一个让用户切换开关状态的按钮:
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// 为了在回调中使用 `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this);
}
handleClick() { //事件处理函数,声明为class中的方法
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
}
render() {
return (
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);
这两种方式是一样的
在这两种情况下,React 的事件对象 e
会被作为第二个参数传递。如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过 bind
的方式,事件对象以及更多的参数将会被隐式的进行传递。
在 React 中,你可以创建不同的组件来封装各种你需要的行为。然后,依据应用的不同状态,你可以只渲染对应状态下的部分内容。
//两个不同的方法
function UserGreeting(props) {
return Welcome back!
;
}
function GuestGreeting(props) {
return Please sign up.
;
}
//函数组件
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return ;
}
return ;
}
ReactDOM.render(
// Try changing to isLoggedIn={true}:
,
document.getElementById('root')
);
使用三目运算符也可以,而且更方便一些
class LoginControl extends React.Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {isLoggedIn: false};
}
handleLoginClick() {
this.setState({isLoggedIn: true});
}
handleLogoutClick() {
this.setState({isLoggedIn: false});
}
render() {
const isLoggedIn = this.state.isLoggedIn;
let button;
if (isLoggedIn) {
button = ;
} else {
button = ;
}
return (
{button}
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);
用button储存最后要渲染哪一个,用State中的值进行判断
&&运算符
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
Hello!
{unreadMessages.length > 0 &&
You have {unreadMessages.length} unread messages.
}
);
}
const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
,
document.getElementById('root')
);
之所以能这样做,是因为在 JavaScript 中,true && expression
总是会返回 expression
, 而 false && expression
总是会返回 false
。
因此,如果条件是 true
,&&
右侧的元素就会被渲染,如果是 false
,React 会忽略并跳过它。
三目运算符
//简单的用法
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
The user is {isLoggedIn ? 'currently' : 'not'} logged in.
);
}
//复杂一点的
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
{isLoggedIn
?
:
}
);
}
在极少数情况下,你可能希望能隐藏组件,即使它已经被其他组件渲染。若要完成此操作,你可以让 render
方法直接返回 null
,而不进行任何渲染。
function WarningBanner(props) {
if (!props.warn) {
return null;
}
return (
Warning!
);
}
class Page extends React.Component {
constructor(props) {
super(props);
this.state = {showWarning: true};
this.handleToggleClick = this.handleToggleClick.bind(this);
}
handleToggleClick() {
this.setState(state => ({
showWarning: !state.showWarning
}));
}
render() {
return (
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);
用map进行遍历,渲染一组组件
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
{number}
);
return (
{listItems}
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
,
document.getElementById('root')
);
map后面还能传入更多参数,比如下标等等
用map可以很方便地把数目未定,但渲染方式相同的组件一个一个渲染出来
key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
{todo.text}
);
当元素没有确定 id 的时候,万不得已你可以使用元素索引 index 作为 key:
不建议用索引作为Key!!!
提取组件
function ListItem(props) {
// 正确!这里不需要指定 key:
return {props.value} ;
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// 正确!key 应该在数组的上下文中被指定
);
return (
{listItems}
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
,
document.getElementById('root')
);
使用 JavaScript 函数可以很方便的处理表单的提交, 同时还可以访问用户填写的表单数据。实现这种效果的标准方式是使用“受控组件”1。
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('提交的名字: ' + this.state.value);
event.preventDefault();
}
render() {
return (
);
}
}
value属性的值由state里的值实时控制
而在 React 中, 使用
value
属性代替。这样,可以使得使用 的表单和使用单行 input 的表单非常类似。
select标签实现受控组件
class FlavorForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'coconut'};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('你喜欢的风味是: ' + this.state.value);
event.preventDefault();
}
render() {
return (
);
}
}
总的来说,这使得 ,
和
之类的标签都非常相似—它们都接受一个
value
属性,可以使用它来实现受控组件。
可以将数组传递到 value
属性中,以支持在 select
标签中选择多个选项:
当需要处理多个 input
元素时,我们可以给每个元素添加 name
属性,并让处理函数根据 event.target.name
的值选择要执行的操作。
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.name === 'isGoing' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
);
}
}
如果你想寻找包含验证、追踪访问字段以及处理表单提交的完整解决方案,使用 Formik 是不错的选择。
通常,多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去。
在 React 中,将多个组件中需要共享的 state 向上移动到它们的最近共同父组件中,便可实现共享 state。这就是所谓的“状态提升”。
因为不同组件间数据是相互独立的,不能互通,因此可以将其数据都向上传入父类,由父类进行分配,就实现了数据共享。
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
this.state = {temperature: '', scale: 'c'};
}
handleCelsiusChange(temperature) {
this.setState({scale: 'c', temperature});
}
handleFahrenheitChange(temperature) {
this.setState({scale: 'f', temperature});
}
render() {
const scale = this.state.scale;
const temperature = this.state.temperature;
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
return (
);
}
}
React 有十分强大的组合模式。我们推荐使用组合而非继承来实现组件间的代码重用。
有些组件无法提前知道子组件的内容,建议这些组件使用一个特殊的 children
prop 来将他们的子组件传递到渲染结果中
function FancyBorder(props) {
return (
{props.children}
);
}
function WelcomeDialog() {
return (
Welcome
Thank you for visiting our spacecraft!
);
}
组件也可以作为对象传入props中。 有些时候,我们会把一些组件看作是其他组件的特殊实例,比如
{props.message}
相当于定制了一个模板,然后根据需要具体化。
{props.message}
基本用不到 React 是用 JavaScript 构建快速响应的大型 Web 应用程序的首选方式 比如要构建一个可搜索的产品数据表格 第一步:将设计好的 UI 划分为组件层级 根据单一功能原则来判定组件的范围。也就是说,一个组件原则上只能负责一个功能。如果它需要负责更多的功能,这时候就应该考虑将它拆分成更小的组件 第二步:用 React 创建一个静态版本 最好将渲染 UI 和添加交互这两个过程分开。这是因为,编写一个应用的静态版本时,往往要编写大量代码,而不需要考虑太多交互细节;添加交互功能时则要考虑大量细节,而不需要编写太多代码。 state 代表了随时间会产生变化的数据,应当仅在实现交互时使用。所以构建应用的静态版本时,你不会用到它。应该只使用props进行父组件与子组件间的数据传递。 编写小型项目时:用自上而下(先构建父组件)的方式构建比较好 编写大型项目时:用自下而上(先构建子组件)的方式构建、并同时为低层组件编写测试比较好 第三步:确定 UI state 的最小(且完整)表示 想要使你的 UI 具备交互功能,需要有触发基础数据模型改变的能力。React 通过实现 state 来完成这个任务 首先要找出应用所需的最少的State值 第四步:确定 state 放置的位置 确定应该将state放在哪个组件里,并且简单细化state应具有的值,添入代码当中 第五步:添加反向数据流 每当用户改变表单的值,我们需要改变 state 来反映用户的当前输入。由于 state 只能由拥有它们的组件进行更改, 用className属性为组件添加CSS的class CSS 的 class 依赖组件的 props 或 state 的情况很常见: 可以通过某些事件来改变样式 React在构建大型项目时具有比较好的效果,但是学起来会比较复杂,需要多看、多写、多练才能掌握熟练 在熟悉基本语法,掌握熟练之后,可以再细化,探索学习更多的东西 在 HTML 中,表单元素(如
JSX 标签中的所有内容都会作为一个 children
prop 传递给 FancyBorder
组件。因为 FancyBorder
将 {props.children}
渲染在一个 function SplitPane(props) {
return (
12.2特例关系
WelcomeDialog
可以说是 Dialog
的特殊实例。function Dialog(props) {
return (
{props.title}
function Dialog(props) {
return (
{props.title}
12.3继承
13.React的设计理念
FilterableProductTable
必须将一个能够触发 state 改变的回调函数(callback)传递给 SearchBar
。我们可以使用输入框的 onChange
事件来监视用户输入的变化,并通知 FilterableProductTable
传递给 SearchBar
的回调函数。然后该回调函数将调用 setState()
,从而更新应用。14.补充
render() {
let className = 'menu';
if (this.props.isActive) {
className += ' menu-active';
}
return Menu
}
15.总结
、
和
)之类的表单元素通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用setState来更新。我们可以把两者结合起来,使 React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。 ↩︎