从头开始使用 React.js
我不再喜欢 React 了,全部使用 Vue.js
简介: React 是由 Facebook 创建和维护的开源视图库。它是渲染现代 Web 应用程序用户界面 (UI) 的绝佳工具。
React 使用 JavaScript 的语法扩展 JSX,它允许你直接在 JavaScript 中编写 HTML。这有几个好处。它让你能够在 HTML 中充分发挥 JavaScript 的编程能力,并有助于保持代码的可读性。JSX 在很大程度上与你已经学过的 HTML 类似,但也有一些关键的区别,这些区别将在这些挑战中介绍。
例如,由于 JSX 是 JavaScript 的语法扩展,因此您实际上可以直接在 JSX 中编写 JavaScript。为此,您只需将要被视为 JavaScript 的代码包含在花括号中即可:{ 'this is treated as JavaScript code' }
。请记住这一点,因为它将在未来的多个挑战中用到。
JSX
const JSX = <h1>Hello JSX</h1>;
const JS = <div>
<h1>Hello</h1>
<p>Hello from p tag</p>
</div>
评论
const JSX = (
<div>
<h1>This is a block of JSX</h1>
<p>Here's a subtitle</p>
{/* this is a comment */}
</div>
);
将 HTML 元素渲染到 DOM
到目前为止,你已经了解到 JSX 是一种在 JavaScript 中编写可读 HTML 的便捷工具。借助 React,我们可以使用 React 的渲染 API(称为 ReactDOM)将此 JSX 直接渲染到 HTML DOM 中。
ReactDOM 提供了一种将 React 元素渲染到 DOM 的简单方法,如下所示:ReactDOM.render(componentToRender, targetNode)
,其中第一个参数是要渲染的 React 元素或组件,第二个参数是要将组件渲染到的 DOM 节点。
正如您所期望的,ReactDOM.render()
必须在 JSX 元素声明之后调用,就像必须在使用变量之前声明变量一样。
const JSX = (
<div id="challenge-node">
<h1>Hello World</h1>
<p>Lets render this to the DOM</p>
</div>
);
// change code below this line
ReactDOM.render(JSX,document.getElementById("challenge-node"))
在 JSX 中定义 HTML 类
现在您已经熟悉了编写 JSX,您可能想知道它与 HTML 有何不同。
到目前为止,HTML 和 JSX 似乎完全相同。
JSX 的一个关键区别是,你不能再使用class
来定义 HTML 类。这是因为class
是 JavaScript 中的保留字。相反,JSX 使用className
。
事实上,JSX 中所有 HTML 属性和事件引用的命名约定都变成了驼峰命名法 (camelCase)。例如,JSX 中的 click 事件是onClick
, 而不是onclick
。同样,onchange
也变成了onChange
。虽然这只是一个细微的差别,但在后续的学习中务必牢记。
const JSX = (
<div className="myDiv">
<h1>Add a class to this div</h1>
</div>
);
自闭合 JSX 标签
const JSX = (
<div>
<h2>Welcome to React!</h2> <br />
<p>Be sure to close all tags!</p>
<hr />
</div>
);
创建无状态函数组件
组件是 React 的核心。React 中的一切都是组件,本文将学习如何创建组件。
创建 React 组件有两种方法。第一种方法是使用 JavaScript 函数。以这种方式定义组件会创建一个无状态函数式组件。应用程序中的状态概念将在后续的挑战中介绍。现在,将无状态组件视为可以接收并渲染数据,但不管理或跟踪数据更改的组件。
要创建具有函数的组件,只需编写一个返回 JSX 或 的 JavaScript 函数null
。需要注意的一点是,React 要求函数名称以大写字母开头。
const MyComponent = function() {
return (
<div>
Hello
</div>
)
}
创建 React 组件
定义 React 组件的另一种方法是使用 ES6class
语法。以下示例中,Kitten
extends React.Component
:
const ChildComponent = () => {
return (
<div>
<p>I am the child</p>
</div>
);
};
class ParentComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h1>I am the parent</h1>
<ChildComponent />
</div>
);
}
};
React 渲染嵌套组件
const TypesOfFruit = () => {
return (
<div>
<h2>Fruits:</h2>
<ul>
<li>Apples</li>
<li>Blueberries</li>
<li>Strawberries</li>
<li>Bananas</li>
</ul>
</div>
);
};
const Fruits = () => {
return (
<div>
<TypesOfFruit />
</div>
);
};
class TypesOfFood extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h1>Types of Food:</h1>
<Fruits />
</div>
);
}
};
另一个例子
class Fruits extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h2>Fruits:</h2>
<NonCitrus />
<Citrus />
</div>
);
}
};
class TypesOfFood extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h1>Types of Food:</h1>
<Fruits />
<Vegetables />
</div>
);
}
};
将 Props 传递给无状态函数组件
在 React 中,你可以将 props(属性)传递给子组件。假设你有一个App
组件,它渲染一个名为 的子组件,Welcome
它是一个无状态函数组件
const CurrentDate = (props) => {
return (
<div>
<p>The current date is: { props.date }</p>
</div>
);
};
class Calendar extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h3>What date is it?</h3>
<CurrentDate date={Date()}/>
</div>
);
}
};
传递数组作为 Props
const List = (props) => {
{ /* change code below this line */ }
return <p>{props.tasks.join(", ")}</p>
{ /* change code above this line */ }
};
class ToDo extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h1>To Do Lists</h1>
<h2>Today</h2>
{ /* change code below this line */ }
<List tasks={["1","1","1"]} />
<h2>Tomorrow</h2>
<List tasks={["1","1","1"]}/>
{ /* change code above this line */ }
</div>
);
}
};
默认道具
const ShoppingCart = (props) => {
return (
<div>
<h1>Shopping Cart Component</h1>
<p>{props.items}</p>
</div>
)
};
// change code below this line
ShoppingCart.defaultProps = {
items : 0
}
覆盖默认 props
const Items = (props) => {
return <h1>Current Quantity of Items in Cart: {props.quantity}</h1>
}
Items.defaultProps = {
quantity: 0
}
class ShoppingCart extends React.Component {
constructor(props) {
super(props);
}
render() {
return <Items quantity={10}/>
}
};
使用 PropTypes 定义你期望的 Prop
输入
import PropTypes from 'prop-types';
代码 :
const Items = (props) => {
return <h1>Current Quantity of Items in Cart: {props.quantity}</h1>
};
Items.propTypes = {
quantity : PropTypes.number.isRequired
}
Items.defaultProps = {
quantity: 0
};
class ShoppingCart extends React.Component {
constructor(props) {
super(props);
}
render() {
return <Items />
}
};
使用 this.props 访问 Props
ES6 类组件使用稍微不同的约定来访问 props。
任何时候在类组件内部引用它时,都要使用this
关键字。要在类组件中访问 props,需要在访问代码前加上this
。例如,如果一个 ES6 类组件有一个名为 的 prop data
,则可以{this.props.data}
使用 JSX 编写。
class ReturnTempPassword extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<p>Your temporary password is: <strong>{this.props.tempPassword}</strong></p>
</div>
);
}
};
class ResetPassword extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h2>Reset Password</h2>
<h3>We've generated a new temporary password for you.</h3>
<h3>Please reset this password from your account settings ASAP.</h3>
<ReturnTempPassword tempPassword="xxxxxxxx" />
</div>
);
}
};
回顾无状态函数组件的 Props 使用
无状态函数组件是指任何接受 props 并返回 JSX 的函数。另一方面,无状态组件React.Component
是指继承自 的类,但不使用内部状态(将在下一个挑战中介绍)。最后,有状态组件是指维护自身内部状态的类组件。您可能会看到有状态组件简称为组件或 React 组件。
一种常见的模式是尽量减少状态,并尽可能创建无状态函数组件。这有助于将状态管理限制在应用程序的特定区域。反过来,这可以让你更轻松地跟踪状态变化如何影响应用程序的行为,从而改善应用程序的开发和维护。
class CampSite extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Camper/>
</div>
);
}
};
class Camper extends React.Component {
constructor(props){
super(props)
}
render(){
return (
<div>
<p>{this.props.name}</p>
</div>
)
}
}
Camper.defaultProps = {
name : "CamperBot"
}
Camper.propTypes = {
name : PropTypes.string.isRequired
}
创建有状态组件
React 中最重要的主题之一是state
。状态包含应用程序需要了解的所有数据,这些数据可能会随时间而变化。您希望应用程序能够响应状态变化并在必要时呈现更新的 UI。React 为现代 Web 应用程序的状态管理提供了一个出色的解决方案。
在 React 组件中,可以通过state
在 中声明组件类的属性来创建状态。这将在组件创建时constructor
初始化组件。该属性必须设置为 JavaScript 代码。声明方式如下:state
state
object
this.state = {
// describe your state here
}
用户界面中的渲染状态
定义组件的初始状态后,您可以在渲染的 UI 中显示它的任何部分。如果组件是有状态的,它将始终能够在state
其render()
方法中访问 中的数据。您可以使用 访问数据this.state
。
如果您想在渲染方法中访问状态值return
,则必须将该值括在花括号中。
State
是 React 组件最强大的功能之一。它允许您跟踪应用中的重要数据,并根据这些数据的变化渲染 UI。如果数据发生变化,UI 也会随之变化。React 使用所谓的虚拟 DOM 来跟踪后台的变化。当状态数据更新时,它会触发使用该数据的组件的重新渲染——包括以 props 形式接收数据的子组件。React 会更新实际的 DOM,但仅在必要时更新。这意味着您无需担心 DOM 的更改。您只需声明 UI 的外观即可。
请注意,如果您将组件设置为有状态,则其他组件将无法感知到它的state
。它state
是完全封装的,或者说是该组件的本地状态,除非您将状态数据作为 传递给子组件props
。封装的概念state
非常重要,因为它允许您编写某些逻辑,然后将该逻辑包含并隔离在代码中的一处。
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'freeCodeCamp'
}
}
render() {
return (
<div>
<h1>{this.state.name}</h1>
</div>
);
}
};
以另一种方式在用户界面中渲染状态
还有另一种访问state
组件的方法。在render()
方法中,在return
语句之前,您可以直接编写 JavaScript。例如,您可以声明函数、从state
或访问数据props
、对这些数据执行计算等等。然后,您可以将任何数据赋值给语句中可以访问的变量return
。
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'freeCodeCamp'
}
}
render() {
const name = this.state.name
return (
<div>
<h1>{this.state.name}</h1>
</div>
);
}
};
this.state 部分 { 重要 }
使用 this.setState 设置状态
还有一种方法可以更改组件的。React 提供了一种名为 的state
更新组件的方法。您可以在组件类中像这样调用该方法:,并传入一个包含键值对的对象。键是状态属性,值是更新后的状态数据。例如,如果我们存储了一个状态并想要更新它,则代码如下:state
setState
setState
this.setState()
username
this.setState({
username: 'Lewis'
});
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'Initial State'
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({
name : "React Rocks!"
})
}
render() {
return (
<div>
<button onClick={this.handleClick}>Click Me</button>
<h1>{this.state.name}</h1>
</div>
);
}
};
将“this”绑定到类方法
除了设置和更新 之外state
,你还可以为组件类定义方法。类方法通常需要使用this
关键字,以便在方法范围内访问类的属性(例如state
和props
)。有几种方法可以让类方法访问this
。
this
一种常见的方法是在构造函数中显式绑定,这样this
在组件初始化时就会绑定到类方法。你可能已经注意到了构造函数中this.handleClick = this.handleClick.bind(this)
对其方法使用的最后一个挑战。这样,当你在类方法中handleClick
调用函数时,引用的就是该类,而不会被this.setState()
this
undefined
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
text: "Hello"
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({
text: "You clicked!"
});
}
render() {
return (
<div>
<button onClick = {this.handleClick}>Click Me</button>
<h1>{this.state.text}</h1>
</div>
);
}
};
使用状态来切换元素
有时,您可能需要在更新状态时了解先前的状态。但是,状态更新可能是异步的——这意味着 React 可能会将多个调用批量合并到单个更新中。这意味着您在计算下一个值时setState()
不能依赖先前的值。因此,您不应该使用如下代码:this.state
this.props
this.setState({
counter: this.state.counter + this.props.increment
});
相反,你应该传递setState
一个允许你访问 state 和 props 的函数。使用带有参数的函数可以setState
保证你使用 state 和 props 的最新值。这意味着上面的代码应该重写为:
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
props
如果您只需要以下state
内容,您也可以使用不带以下内容的表格:
this.setState(state => ({
counter: state.counter + 1
}));
柜台
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
this.increment = this.increment.bind(this);
this.decrement = this.decrement.bind(this);
this.reset = this.reset.bind(this);
}
reset() {
this.setState({
count: 0
});
}
increment() {
this.setState(state => ({
count: state.count + 1
}));
}
decrement() {
this.setState(state => ({
count: state.count - 1
}));
}
render() {
return (
<div>
<button className='inc' onClick={this.increment}>Increment!</button>
<button className='dec' onClick={this.decrement}>Decrement!</button>
<button className='reset' onClick={this.reset}>Reset</button>
<h1>Current Count: {this.state.count}</h1>
</div>
);
}
};
React:创建受控输入
class ControlledInput extends React.Component {
constructor(props) {
super(props);
this.state = {
input: ''
};
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
this.setState({
input: event.target.value
});
}
render() {
return (
<div>
<input value = {this.state.input} onChange = {this.handleChange.bind(this)}/>
<h4>Controlled Input:</h4>
<p>{this.state.input}</p>
</div>
);
}
};
React:创建受控表单(第二部分)
class MyForm extends React.Component {
constructor(props) {
super(props);
this.state = {
input: '',
submit: ''
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({
input: event.target.value
});
}
handleSubmit(event) {
event.preventDefault()
this.setState({
submit: this.state.input
});
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<input
value={this.state.input}
onChange={this.handleChange} />
<button type='submit'>Submit!</button>
</form>
<h1>{this.state.submit}</h1>
</div>
);
}
};
将状态作为道具传递给子组件
class MyApp extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'CamperBot'
}
}
render() {
return (
<div>
<Navbar name={this.state.name} />
</div>
);
}
};
class Navbar extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h1>Hello, my name is: {this.props.name} </h1>
</div>
);
}
};
将回调作为 Props 传递
class MyApp extends React.Component {
constructor(props) {
super(props);
this.state = {
inputValue: ''
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({
inputValue: event.target.value
});
}
render() {
return (
<div>
<GetInput
input={this.state.inputValue}
handleChange={this.handleChange}/>
<RenderInput
input={this.state.inputValue}/>
</div>
);
}
};
class GetInput extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h3>Get Input:</h3>
<input
value={this.props.input}
onChange={this.props.handleChange}/>
</div>
);
}
};
class RenderInput extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h3>Input Render:</h3>
<p>{this.props.input}</p>
</div>
);
}
};
组件生命周期
例子 :
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
message: ""
};
this.handleEnter = this.handleEnter.bind(this);
this.handleKeyPress = this.handleKeyPress.bind(this);
}
// change code below this line
componentDidMount() {
document.addEventListener("keydown", this.handleKeyPress);
}
componentWillUnmount() {
document.removeEventListener("keydown", this.handleKeyPress);
}
// change code above this line
handleEnter() {
this.setState({
message: this.state.message + "You pressed the enter key! "
});
}
handleKeyPress(event) {
if (event.keyCode === 13) {
this.handleEnter();
}
}
render() {
return (
<div>
<h1>{this.state.message}</h1>
</div>
);
}
}
使用 shouldComponentUpdate 优化重新渲染
class OnlyEvens extends React.Component {
constructor(props) {
super(props);
}
shouldComponentUpdate(nextProps) {
if (nextProps.value % 2 == 0) {
return true;
}
return false;
}
componentDidUpdate() {
console.log('Component re-rendered.');
}
render() {
return <h1>{this.props.value}</h1>
}
};
class Controller extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 0
};
this.addValue = this.addValue.bind(this);
}
addValue() {
this.setState({
value: this.state.value + 1
});
}
render() {
return (
<div>
<button onClick={this.addValue}>Add</button>
<OnlyEvens value={this.state.value}/>
</div>
);
}
};
内联样式
const styles = {
color: 'purple',
fontSize: 40,
border: "2px solid purple",
};
class Colorful extends React.Component {
render() {
// change code below this line
return (
<div style={styles}>Style Me!</div>
);
// change code above this line
}
};
在 React Render 方法中使用高级 JavaScript
const inputStyle = {
width: 235,
margin: 5
}
class MagicEightBall extends React.Component {
constructor(props) {
super(props);
this.state = {
userInput: '',
randomIndex: ''
}
this.ask = this.ask.bind(this);
this.handleChange = this.handleChange.bind(this);
}
ask() {
if (this.state.userInput) {
this.setState({
randomIndex: Math.floor(Math.random() * 20),
userInput: ''
});
}
}
handleChange(event) {
this.setState({
userInput: event.target.value
});
}
render() {
const possibleAnswers = [
'It is certain',
];
const answer = possibleAnswers[this.state.randomIndex];
return (
<div>
<input
type="text"
value={this.state.userInput}
onChange={this.handleChange}
style={inputStyle} /><br />
<button onClick={this.ask}>
Ask the Magic Eight Ball!
</button><br />
<h3>Answer:</h3>
<p>
{answer}
</p>
</div>
);
}
};
条件渲染
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
display: true
}
this.toggleDisplay = this.toggleDisplay.bind(this);
}
toggleDisplay() {
this.setState({
display: !this.state.display
});
}
render() {
// change code below this line
if (this.state.display) {
return (
<div>
<button onClick={this.toggleDisplay}>Toggle Display</button>
<h1>Displayed!</h1>
</div>
);
} else {
return (
<div>
<button onClick={this.toggleDisplay}>Toggle Display</button>
</div>
);
}
}
};
使用 && 实现更简洁的条件
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
display: true
}
this.toggleDisplay = this.toggleDisplay.bind(this);
}
toggleDisplay() {
this.setState(state => ({
display: !state.display
}));
}
render() {
// change code below this line
return (
<div>
<button onClick={this.toggleDisplay}>Toggle Display</button>
{this.state.display && <h1>Displayed!</h1>}
</div>
);
}
};
使用三元表达式进行条件渲染
const inputStyle = {
width: 235,
margin: 5
}
class CheckUserAge extends React.Component {
constructor(props) {
super(props);
this.state = {
userAge: '',
input: ''
}
this.submit = this.submit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({
input: e.target.value,
userAge: ''
});
}
submit() {
this.setState(state => ({
userAge: state.input
}));
}
render() {
const buttonOne = <button onClick={this.submit}>Submit</button>;
const buttonTwo = <button>You May Enter</button>;
const buttonThree = <button>You Shall Not Pass</button>;
return (
<div>
<h3>Enter Your Age to Continue</h3>
<input
style={inputStyle}
type="number"
value={this.state.input}
onChange={this.handleChange} /><br />
{
this.state.userAge === ''
? buttonOne
: this.state.userAge >= 18
? buttonTwo
: buttonThree
}
</div>
);
}
};
根据 props 进行条件渲染
class Results extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<h1>
{
this.props.fiftyFifty ?
'You Win!' :
'You Lose!'
}
</h1>
)
};
};
class GameOfChance extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 1
}
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({
counter: this.state.counter + 1
});
}
render() {
const expression = Math.random() >= .5;
return (
<div>
<button onClick={this.handleClick}>Play Again</button>
<Results fiftyFifty={expression} />
<p>{'Turn: ' + this.state.counter}</p>
</div>
);
}
};
根据组件状态有条件地更改内联 CSS
class GateKeeper extends React.Component {
constructor(props) {
super(props);
this.state = {
input: ''
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({ input: event.target.value })
}
render() {
let inputStyle = {
border: '1px solid black'
};
// change code below this line
if (this.state.input.length > 15) {
inputStyle = {
border: '3px solid red'
};
}
// change code above this line
return (
<div>
<h3>Don't Type Too Much:</h3>
<input
type="text"
style={inputStyle}
value={this.state.input}
onChange={this.handleChange} />
</div>
);
}
};
使用 Array.map() 动态渲染元素
const textAreaStyles = {
width: 235,
margin: 5
};
class MyToDoList extends React.Component {
constructor(props) {
super(props);
// change code below this line
this.state = {
userInput: '',
toDoList: []
}
// change code above this line
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleSubmit() {
const itemsArray = this.state.userInput.split(',');
this.setState({
toDoList: itemsArray
});
}
handleChange(e) {
this.setState({
userInput: e.target.value
});
}
render() {
const items = this.state.toDoList.map(i => <li>{i}</li>); // change code here
return (
<div>
<textarea
onChange={this.handleChange}
value={this.state.userInput}
style={textAreaStyles}
placeholder="Separate Items With Commas" /><br />
<button onClick={this.handleSubmit}>Create List</button>
<h1>My "To Do" List:</h1>
<ul>
{items}
</ul>
</div>
);
}
};
赋予兄弟元素唯一的键属性
const renderFrameworks = frontEndFrameworks.map((item) =>
<li key={item+1}>{item}</li>
);
使用 Array.filter() 动态过滤数组
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
users: [
{
username: 'Jeff',
online: true
},
{
username: 'Alan',
online: false
},
{
username: 'Mary',
online: true
},
{
username: 'Jim',
online: false
},
{
username: 'Sara',
online: true
},
{
username: 'Laura',
online: true
}
]
}
}
render() {
const usersOnline = this.state.users.filter(i => i.online == true); // change code here
const renderOnline = usersOnline.map((i) => <li key={i.username + 1}>{i.username}</li>); // change code here
return (
<div>
<h1>Current Online Users:</h1>
<ul>
{renderOnline}
</ul>
</div>
);
}
};
使用 renderToString 在服务器上渲染 React
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div/>
}
};
// change code below this line
ReactDOMServer.renderToString(<App />);