现在你已经熟悉了 HTML、CSS 和 JavaScript,通过学习业内一些最流行的前端库来提高你的技能。 在前端开发库认证中,你将学习如何使用 Bootstrap 快速设置网站样式。你还将学习如何向 CSS 样式添加逻辑并使用 Sass 扩展它们。 稍后,你将构建购物车和其他应用程序,以学习如何使用 React 和 Redux 创建功能强大的单页应用程序 (SPA)。
sass 是 scss 升级版 sass 可以没有花括号
{}
**, 也无需分号结尾**;
, 这个特性后续不再说明 注意: 发现很多其实是 SCSS, 教程不太严谨 Sass, 是 CSS 的语言扩展。它添加了基本 CSS 中不可用的功能,使你可以更轻松地简化和维护项目的样式表。 如何将数据存储在变量中、嵌套 CSS、使用 mixins 创建可重用的样式、为样式添加逻辑和循环等等。
Sass 与 CSS 不同的一个功能是它使用变量。 它们被声明并设置为存储数据,类似于 JavaScript。 在 JavaScript 中,变量是使用
let
和const
关键字定义的。 在 Sass 中,变量以$
开头,后跟变量名称。 申明变量 变量赋值用冒号 :
/* 注意: 这里中间间隔用的是逗号 , 而不是空格 */
/* 变量值 也无需加双引号/单引号, 就像是正常的 css 属性值一样 */
$main-fonts: Arial, sans-serif;
$headings-color: green;
使用变量
h1 {
/* 一般 css 这里多个属性值使用空格间隔 */
font-family: $main-fonts;
color: $headings-color;
}
变量有用的一个例子是当许多元素需要相同的颜色时。 如果更改了该颜色,则编辑代码的唯一位置是变量值。 创建一个变量
$text-color
并将其设置为red
。 然后将.blog-post
和h2
的color
属性的值更改为$text-color
变量。
<style type='text/scss'>
$text-color: red;
.header{
text-align: center;
}
.blog-post, h2 {
color: $text-color;
}
</style>
<h1 class="header">Learn Sass</h1>
<div class="blog-post">
<h2>Some random title</h2>
<p>This is a paragraph with some random text in it</p>
</div>
<div class="blog-post">
<h2>Header #2</h2>
<p>Here is some more random text.</p>
</div>
<div class="blog-post">
<h2>Here is another header</h2>
<p>Even more random text within a paragraph</p>
</div>
传统 CSS 中, 通常,每个元素都针对不同的行来设置其样式,如下所示:
nav {
background-color: red;
}
nav ul {
list-style: none;
}
nav ul li {
display: inline-block;
}
对于大型项目,CSS 文件将有许多行和规则。 这就是嵌套可以通过在相应的父元素中放置子样式规则来帮助组织代码的地方:
nav {
background-color: red;
ul {
list-style: none;
li {
display: inline-block;
}
}
}
实践
<style type='text/scss'>
.blog-post {
h1 {
text-align: center;
color: blue;
}
p {
font-size: 20px;
}
}
</style>
<div class="blog-post">
<h1>Blog Title</h1>
<p>This is a paragraph</p>
</div>
在 Sass 中,mixin 是一组可以在整个样式表中重用的 CSS 声明。 较新的 CSS 功能需要时间才能完全采用并准备好在所有浏览器中使用。 随着功能添加到浏览器中,使用它们的 CSS 规则可能需要供应商前缀。 考虑
box-shadow
:
div {
-webkit-box-shadow: 0px 0px 4px #fff;
-moz-box-shadow: 0px 0px 4px #fff;
-ms-box-shadow: 0px 0px 4px #fff;
box-shadow: 0px 0px 4px #fff;
}
为所有具有
box-shadow
的元素重写此规则或更改每个值以测试不同的效果需要大量键入。 Mixins 就像 CSS 的函数。 如下所示:
@mixin box-shadow($x, $y, $blur, $c){
-webkit-box-shadow: $x $y $blur $c;
-moz-box-shadow: $x $y $blur $c;
-ms-box-shadow: $x $y $blur $c;
box-shadow: $x $y $blur $c;
}
定义以
@mixin
开头,后跟自定义名称。 参数(上例中的$x
、$y
、$blur
和$c
)是可选的。 现在,每当需要box-shadow
规则时, 只需一行调用 mixin 即可取代键入所有 vendor 前缀的行。 使用@include
指令调用mixin
:
div {
@include box-shadow(0px, 0px, 4px, #fff);
}
实践 为
border-radius
编写一个mixin
并为其指定一个$radius
参数。 它应使用示例中的所有 vendor 前缀。 然后使用border-radius
mixin 使#awesome
元素的边框半径为15px
。
<style type='text/scss'>
@mixin border-radius($radius) {
-webkit-border-radius: $radius;
-moz-border-radius: $radius;
-ms-border-radius: $radius;
border-radius: $radius;
}
#awesome {
width: 150px;
height: 150px;
background-color: green;
@include border-radius(15px);
}
</style>
<div id="awesome"></div>
/* 定义函数 */
@mixin custom-mixin-name($param1, $param2, ....) {
// CSS Properties Here...
}
/* 使用函数 */
element {
@include custom-mixin-name(value1, value2, ....);
}
一个官网示例: Sass: @mixin and @include 可选参数 CSS
.mail-icon {
text-indent: -99999em;
overflow: hidden;
text-align: left;
background-image: url("/images/mail.svg");
background-repeat: no-repeat;
background-position: 0 50%;
}
SCSS
@mixin replace-text($image, $x: 50%, $y: 50%) {
text-indent: -99999em;
overflow: hidden;
text-align: left;
background: {
image: $image;
repeat: no-repeat;
position: $x $y;
}
}
.mail-icon {
@include replace-text(url("/images/mail.svg"), 0);
}
Sass
@mixin replace-text($image, $x: 50%, $y: 50%)
text-indent: -99999em
overflow: hidden
text-align: left
background:
image: $image
repeat: no-repeat
position: $x $y
.mail-icon
@include replace-text(url("/images/mail.svg"), 0)
@if
和 @else
为样式添加逻辑Sass 中的
@if
指令对于测试特定情况很有用 它的工作方式就像 JavaScript 中的if
语句一样。
@mixin make-bold($bool) {
@if $bool == true {
font-weight: bold;
}
}
就像在 JavaScript 中一样,
@else if
和@else
指令测试更多条件:
@mixin text-effect($val) {
@if $val == danger {
color: red;
}
@else if $val == alert {
color: yellow;
}
@else if $val == success {
color: green;
}
@else {
color: black;
}
}
实践 创建一个名为
border-stroke
的 mixin,该 mixin 采用参数$val
。 mixin 应使用@if
、@else if
和@else
指令检查以下条件:
light - 1px solid black
medium - 3px solid black
heavy - 6px solid black
如果
$val
参数值不是light
、medium
或heavy
, 则border
属性应设置为none
。
<style type='text/scss'>
@mixin border-stroke($val) {
@if $val == light {
border: 1px solid black;
}
@else if $val == medium {
border: 3px solid black;
}
@else if $val == heavy {
border: 6px solid black;
}
@else {
border: none;
}
}
#box {
width: 150px;
height: 150px;
background-color: red;
@include border-stroke(medium);
}
</style>
<div id="box"></div>
@for
创建 Sass 循环
@for
有两种方式:
from A through B
[A, B]
from A to B
[A, b)
@for $i from 1 through 3 {
// some CSS
}
// 1 2 3
@for $i from 1 to 3 {
// some CSS
}
// 1 2
PS:可以直接带上单位 (
%, px 等
) 进行计算
@for $i from 1 through 12 {
.col-#{$i} { width: 100%/12 * $i; }
}
@for $i from 1 to 12 {
.col-#{$i} { width: 100%/12 * $i; }
}
该
#{$i}
部分是将变量 (i
) 与文本组合以生成字符串 的语法。 当 Sass 文件转换为 CSS 时,它看起来像这样:
.col-1 {
width: 8.33333%;
}
.col-2 {
width: 16.66667%;
}
...
.col-12 {
width: 100%;
}
这是一个创建
grid
布局的好方法, 实践 使用@for
指令用$j
从1
到6
(不包括6
), 创建 5 个类,从.text-1
到.text-5
, 每个都有font-size
值为15px*$j
<style type='text/scss'>
@for $j from 1 to 6 {
.text-#{$j} {
font-size: 15px * $j;
}
}
</style>
<p class="text-1">Hello</p>
<p class="text-2">Hello</p>
<p class="text-3">Hello</p>
<p class="text-4">Hello</p>
<p class="text-5">Hello</p>
@each
映射列表中的项Sass 还提供了
@each
循环遍历列表 或 映射中每个项 的指令。 在每次迭代中,变量被分配给列表或映射中的当前值。
@each $color in blue, red, green {
.#{$color}-text {color: $color;}
}
映射 的语法略有不同。 一个例子:
$colors: (color1: blue, color2: red, color3: green);
@each $key, $color in $colors {
.#{$color}-text {color: $color;}
}
请注意,需要该
$key
变量来引用映射中的键。 否则,编译后的 CSS 将包含color1
,color2
...。 以上两个代码示例都转换为以下 CSS:
.blue-text {
color: blue;
}
.red-text {
color: red;
}
.green-text {
color: green;
}
实践 编写一个
@each
遍历列表的指令:blue, black, red
并将每个变量分配给一个.color-bg
类, 其中color
每个项目的部分都会发生变化。 每个类都应该设置background-color
各自的颜色。
<style type='text/scss'>
$colors: blue, black, red;
@each $color in $colors {
.#{$color}-bg {
background-color: $color;
}
}
div {
height: 200px;
width: 200px;
}
</style>
<div class="blue-bg"></div>
<div class="black-bg"></div>
<div class="red-bg"></div>
@while
该
@while
指令是一个选项,具有与 JavaScriptwhile
循环类似的功能。 它会创建 CSS 规则,直到满足条件为止。 简单网格系统
$x: 1;
@while $x < 13 {
.col-#{$x} { width: 100%/12 * $x;}
$x: $x + 1;
}
首先,定义一个变量
$x
并将其设置为 1。 接下来,使用@while
指令创建网格系统while
$x
小于 13。 在为 设置 CSS 规则后width
,$x
递增 1 以避免无限循环。 实践 用于@while
创建一系列具有不同font-sizes
.text-1
到text-5
。 然后设置font-size
为15px
乘以当前索引号。 确保避免无限循环!
<style type='text/scss'>
$x: 1;
@while $x <= 5 {
.text-#{$x} {
font-size: 15px * $x;
}
$x: $x + 1;
}
</style>
<p class="text-1">Hello</p>
<p class="text-2">Hello</p>
<p class="text-3">Hello</p>
<p class="text-4">Hello</p>
<p class="text-5">Hello</p>
Sass 中的 Partials 是包含 CSS 代码段的单独文件。 这些被导入并在其他 Sass 文件中使用。 这是将类似代码分组到一个模块中以保持其组织性的好方法。 partials 的名称以下划线 (
_
) 字符开头,告诉 Sass 这是 CSS 的一小段,不要将其转换为 CSS 文件。 此外,Sass 文件以文件扩展名结尾.scss
。 要将部分代码放入另一个 Sass 文件,请使用该@import
指令。 例如, 如果你所有的 mixins 都保存在名为“_mixins.scss”
的部分中, 并且在“main.scss”
文件中需要它们, 那么在主文件中使用它们的方法如下:
@import 'mixins'
请注意,语句中不需要下划线和文件扩展名
import
Sass 理解它是 partials 的。 一旦将 partials 导入到文件中,所有变量、mixin 和其他代码都可以使用。 实践 编写一条@import
语句,将 partial named 导入_variables.scss
到main.scss
文件中。
<!-- The main.scss file -->
@import 'variables'
@extend
**) 到另一个元素个人理解: @extend 更像是继承 Sass 有一个特性
extend
,可以很容易地从一个元素借用 CSS 规则并在另一个元素的基础上构建。 例如, 下面的 CSS 规则块为一个.panel
类设置样式。 它有一个background-color
,height
和border
.panel {
background-color: red;
height: 70px;
border: 2px solid green;
}
现在您想要另一个的面板名为
.big-panel
。 它具有与 相同的基本属性.panel
,但还需要一个width
和font-size
。 可以从 复制和粘贴初始 CSS 规则.panel
,但是随着您添加更多类型的面板,代码会变得重复。 该extend
指令是重用为一个元素编写的规则,然后为另一个元素添加更多规则的简单方法:
.big-panel {
@extend .panel;
width: 150px;
font-size: 2em;
}
除了新样式之外,
.big-panel
与.panel
具有相同的属性 实践 创建一个 CSS 类名为.info-important
, 扩展自.info
, 并有background-color: magenta
<style type='text/scss'>
h3{
text-align: center;
}
.info{
width: 200px;
border: 1px solid black;
margin: 0 auto;
}
/* 使用 @extend */
.info-important {
@extend .info;
background-color: magenta;
}
</style>
<h3>Posts</h3>
<div class="info-important">
<p>This is an important post. It should extend the class ".info" and have its own CSS styles.</p>
</div>
<div class="info">
<p>This is a simple post. It has basic styling and can be extended for other uses.</p>
</div>
{ /* 我是注释 */ }
要将注释放在 JSX 中,请使用语法 {/* */}
环绕注释文本。
const JSX = (
<div>
<h1>This is a block of JSX</h1>
<p>Here's a subtitle</p>
{ /* 我是 JSX 中的注释 */ }
</div>
);
在 JSX 中, 任何 JSX 元素都可以使用自闭合标记编写,并且每个元素都必须闭合。 例如,换行标记必须始终写为
<br />
,才能成为可以转译的有效 JSX。 而在 HTML 中, 例如,换行标记可以写为<br>
或<br />
,但绝不应写为<br></br>
,因为它不包含任何内容。
有两种方法可以创建 React 组件。第一种方法是使用 JavaScript 函数。 以这种方式定义组件会创建一个无状态的功能组件。 现在,将无状态组件视为可以接收数据并呈现数据,但不管理或跟踪对该数据的更改的组件。
要创建带有函数的组件,您只需编写一个返回 JSX 或 null
的 JavaScript 函数。需要注意的重要一点是,React 要求你的函数名称以大写字母开头。
const DemoComponent = function() {
return (
<div className='customClass' />
);
};
定义 React 组件的另一种方法是使用 ES6 class
语法。在以下示例中, Kitten
扩展 React.Component
:
class Kitten extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<h1>Hi</h1>
);
}
}
props
值: MyComponent.defaultProps
默认 props
值
MyComponent.defaultProps = { location: 'San Francisco' }
定义 location
默认值为 'San Francisco'
,
React 分配默认值为: undefined
, 但若指定分配 null
, 则会保持 null
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() {
{ /* Change code below this line */ }
return <Items quantity={10} />
{ /* Change code above this line */ }
}
};
propTypes
限制 props
通过
propTypes
限制props
参考:Typechecking With PropTypes – React
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
};
this.props
访问 props
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
{ /* Change code below this line */ }
<Welcome name="world"/>
{ /* Change code above this line */ }
</div>
);
}
};
class Welcome extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
{ /* Change code below this line */ }
<p>Hello, <strong>{this.props.name}</strong>!</p>
{ /* Change code above this line */ }
</div>
);
}
};
propTypes, defaultProps
const Camper = props => <p>{props.name}</p>;
Camper.propTypes = {
name: PropTypes.string.isRequired
};
Camper.defaultProps = {
name: "CamperBot"
};
class CampSite extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Camper name="test"/>
</div>
);
}
};
ReactDOM.render(<CampSite />, document.getElementById("challenge-node"));
class StatefulComponent extends React.Component {
constructor(props) {
super(props);
// Only change code below this line
this.state = {
firstName: "Hello"
};
// Only change code above this line
}
render() {
return (
<div>
<h1>{this.state.firstName}</h1>
</div>
);
}
};
setState
更新 state
this.setState({
username: 'Lewis'
});
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
text: "Hello"
};
// Change code below this line
this.handleClick = this.handleClick.bind(this);
// Change code above this line
}
handleClick() {
this.setState({
text: "You clicked!"
});
}
render() {
return (
<div>
{ /* Change code below this line */ }
<button onClick={this.handleClick}>Click Me</button>
{ /* Change code above this line */ }
<h1>{this.state.text}</h1>
</div>
);
}
};
有时候,需要访问之前的
state
用于更新现在的state
, 然而,state
更新可能是异步的,这意味着 React 可能批量多个setState()
呼叫在单个更新,这意味着你不能依赖于之前的值:this.state
或this.props
当用于计算下一个值时, 因此你不能下面这样写:
// 错误 示范
this.setState({
counter: this.state.counter + this.props.increment
});
// 正确 示范
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
也可以如下
this.setState(state => ({
counter: state.counter + 1
}));
注意:必须将函数返回值(对象)包裹在
()
中
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
visibility: false
};
// Change code below this line
this.toggleVisibility = this.toggleVisibility.bind(this);
// Change code above this line
}
// Change code below this line
toggleVisibility() {
this.setState(state => ({
visibility: !state.visibility
}));
}
// Change code above this line
render() {
if (this.state.visibility) {
return (
<div>
<button onClick={this.toggleVisibility}>Click Me</button>
<h1>Now you see me!</h1>
</div>
);
} else {
return (
<div>
<button onClick={this.toggleVisibility}>Click Me</button>
</div>
);
}
}
}
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
// Change code below this line
this.increment = this.increment.bind(this);
this.decrement = this.decrement.bind(this);
this.reset = this.reset.bind(this);
// Change code above this line
}
// Change code below this line
increment() {
this.setState(state => ({
count: state.count + 1
}));
}
decrement() {
this.setState(state => ({
count: state.count - 1
}));
}
reset() {
this.setState(state => ({
count: 0
}))
}
// Change code above this line
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>
);
}
};
input
class ControlledInput extends React.Component {
constructor(props) {
super(props);
this.state = {
input: ''
};
// Change code below this line
this.handleChange = this.handleChange.bind(this);
// Change code above this line
}
// Change code below this line
handleChange(event) {
this.setState(state => ({
input: event.target.value
}));
}
// Change code above this line
render() {
return (
<div>
{ /* Change code below this line */}
<input value={this.state.input} onChange={this.handleChange} />
{ /* Change code above this line */}
<h4>Controlled Input:</h4>
<p>{this.state.input}</p>
</div>
);
}
};
Form
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) {
// Change code below this line
event.preventDefault();
this.setState(state => ({
submit: state.input
}));
// Change code above this line
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
{/* Change code below this line */}
<input value={this.state.input} onChange={this.handleChange} />
{/* Change code above this line */}
<button type='submit'>Submit!</button>
</form>
{/* Change code below this line */}
<h1>{this.state.submit}</h1>
{/* Change code above this line */}
</div>
);
}
}
class MyApp extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'CamperBot'
}
}
render() {
return (
<div>
{/* Change code below this line */}
<Navbar name={this.state.name} />
{/* Change code above this line */}
</div>
);
}
};
class Navbar extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
{/* Change code below this line */}
<h1>Hello, my name is: {this.props.name}</h1>
{/* Change code above this line */}
</div>
);
}
};
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>
{ /* Change code below this line */ }
<GetInput input={this.state.inputValue} handleChange={this.handleChange} />
<RenderInput input={this.state.inputValue} />
{ /* Change code above this line */ }
</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>
);
}
};
componentWillMount
Note: The
componentWillMount
Lifecycle method will be deprecated in a future version of 16.X and removed in version 17. Learn more in this article
componentWillMount()
在 render()
之前被调用
componentDidMount
组件被挂载到 DOM 后
componentDidMount()
被调用, 任何对setState()
都会触发组件的重新渲染, 可以在这里获取异步 API 的数据
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
activeUsers: null
};
}
componentDidMount() {
setTimeout(() => {
this.setState({
activeUsers: 1273
});
}, 2500);
}
render() {
return (
<div>
{/* Change code below this line */}
<h1>Active Users: {this.state.activeUsers}</h1>
{/* Change code above this line */}
</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((state) => ({
message: 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
优化重新渲染此方法是优化性能的有用方法。 例如,默认行为是组件在收到新的
props
时重新渲染,即使props
没有更改也是如此。 你可以使用shouldComponentUpdate()
通过比较props
来防止这种情况。 该方法必须返回一个boolean
值,告诉 React 是否更新组件。 你可以将当前 props(this.props
)与下一个props (nextProps
) 进行比较, 以确定是否需要更新,并相应地返回true
或false
。 使OnlyEvens
仅在其新props
的value
为偶数时才更新
class OnlyEvens extends React.Component {
constructor(props) {
super(props);
}
shouldComponentUpdate(nextProps, nextState) {
console.log('Should I update?');
// Change code below this line
if (nextProps.value %2 == 0) {
return true;
}
return false;
// Change code above this line
}
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(state => ({
value: state.value + 1
}));
}
render() {
return (
<div>
<button onClick={this.addValue}>Add</button>
<OnlyEvens value={this.state.value} />
</div>
);
}
}
不会再显示奇数, 点击 2 次, 从 20 到 22
Inline Styles
**)HTML 中内联样式的示例
<div style="color: yellow; font-size: 16px">Mellow Yellow</div>
JSX 元素使用
style
属性,但由于 JSX 的转译方式,你无法将该值设置为string
。 相反,你把它设置为等于JavaScriptobject
。
<div style={{color: "yellow", fontSize: 16}}>Mellow Yellow</div>
请注意,你可以选择将字体大小设置为数字,省略单位
px
,或将其写为72px
。
// Change code above this line
// 需要写在外部
const styles = {
color: 'purple',
fontSize: 40,
border: "2px solid purple",
};
class Colorful extends React.Component {
render() {
// Change code below this line
// 写在这里: undefined, 无法在下面找到, 说明下面 JSX 作用域并不在此方法内, 但很奇怪, 在后面的例子中, answer 又可以直接使用
// const styles = {
// color: 'purple',
// fontSize: 40,
// border: "2px solid purple",
// };
return (
<div style={styles}>Style Me!</div>
);
// Change code above this line
}
};
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',
'It is decidedly so',
'Without a doubt',
'Yes, definitely',
'You may rely on it',
'As I see it, yes',
'Outlook good',
'Yes',
'Signs point to yes',
'Reply hazy try again',
'Ask again later',
'Better not tell you now',
'Cannot predict now',
'Concentrate and ask again',
"Don't count on it",
'My reply is no',
'My sources say no',
'Most likely',
'Outlook not so good',
'Very doubtful'
];
const answer = possibleAnswers[this.state.randomIndex]; // Change this line
// 很奇怪, 为什么这里又可以直接使用 answer
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>
{/* Change code below this line */}
{answer}
{/* Change code above this line */}
</p>
</div>
);
}
}
if-else
条件渲染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
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>
)
}
}
};
&&
简化条件{condition && <p>markup</p>}
如果
condition
为true
,则将返回标记。 如果条件为false
,则操作将在计算condition
后立即返回false
,并且不返回任何内容。
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>
);
}
};
三元表达式
进行条件渲染condition ? expressionIfTrue : expressionIfFalse;
const inputStyle = {
width: 235,
margin: 5
};
class CheckUserAge extends React.Component {
constructor(props) {
super(props);
// Change code below this line
this.state = {
input: '',
userAge: ''
};
// Change code above this line
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 />
{/* Change code below this line */}
{ /* 注意: 可以直接使用 buttonOne, 而不是 <buttonOne /> */ }
{
this.state.userAge === ''
? buttonOne
: this.state.userAge >= 18
? buttonTwo
: buttonThree
}
{/* Change code above this line */}
</div>
);
}
}
props
有条件地渲染class Results extends React.Component {
constructor(props) {
super(props);
}
render() {
{/* Change code below this line */}
return (
<h1>
{this.props.fiftyFifty ? "You Win!" : "You Lose!"}
</h1>
);
{/* Change code above this line */}
}
}
class GameOfChance extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 1
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => {
// Complete the return statement:
return {
counter: prevState.counter + 1
}
});
}
render() {
const expression = Math.random() >= 0.5; // Change this line
return (
<div>
<button onClick={this.handleClick}>Play Again</button>
{/* Change code below this line */}
<Results fiftyFifty={expression} />
{/* Change code above this line */}
<p>{'Turn: ' + this.state.counter}</p>
</div>
);
}
}
输入框中键入的文本超过 15 个字符,则将此边框设置为红色样式。
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
inputStyle = this.state.input.length > 15 ? { border: '3px solid red' } : inputStyle;
// 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(item => <li>{item}</li>); // Change this line
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>
);
}
}
key
属性创建元素数组时,每个元素都需要将
key
属性设置为唯一值。 React 使用这些键来跟踪添加、更改或删除了哪些项。 这有助于在以任何方式修改列表时使重新渲染过程更高效。 注意:key
只需要在同级元素之间是唯一的,它们在应用程序中不需要全局唯一。 通常,你希望使key
能唯一标识正在呈现的元素。 作为最后的手段,可以使用数组索引,但通常应尝试使用唯一标识。
const frontEndFrameworks = [
'React',
'Angular',
'Ember',
'Knockout',
'Backbone',
'Vue'
];
function Frameworks() {
const renderFrameworks = frontEndFrameworks.map((item, index) =>
<li key={index}>{item}</li>
); // Change this line
return (
<div>
<h1>Popular Front End JavaScript Frameworks</h1>
<ul>
{renderFrameworks}
</ul>
</div>
);
};
Array.filter()
动态过滤数组// 取出 users 中所有在线用户:
// online: Boolean
let onlineUsers = users.filter(user => user.online);
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(user => user.online); // Change this line
const renderOnline = usersOnline.map((item, index) => <li key={index}>{item.username}</li>); // Change this line
return (
<div>
<h1>Current Online Users:</h1>
<ul>{renderOnline}</ul>
</div>
);
}
}
renderToString
在服务端渲染 React至此, 渲染 React 组件都是在客户端(客户浏览器), 通常来说你完全可以这么做, 然而,有些情况需要渲染 React 组件在服务端, 因为 React 是一个 JavaScript 视图库,并且你可以使用 Node.js 在服务端运行 JavaScript, 事实上, React 提供了
renderToString()
方法用于此种情况。
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div/>
}
};
// Change code below this line
ReactDOMServer.renderToString(<App />);
Redux Store
Redux
是一个状态管理框架,可以与许多不同的 Web 技术一起使用,包括 React 在 Redux 中,有一个状态对象负责应用程序的整个状态。 这意味着, 如果你有一个包含十个组件的 React 应用程序, 并且每个组件都有自己的本地状态, 则应用程序的整个状态将由 Reduxstore
中的单个状态对象定义。 这也意味着, 每当应用的任何部分想要更新状态时, 它都必须通过Redux store
来实现。 通过单向数据流,可以更轻松地跟踪应用中的状态管理。 Reduxstore
是保存和管理应用程序state
的对象。 Redux 对象上有一个名为createStore()
的方法,用于创建 Reduxstore
。 此方法将reducer
函数作为必需参数。reducer
函数将在后面的挑战中介绍,并且已经在代码编辑器中为您定义。 它只是将state
作为参数并返回state
。 声明一个store
变量并将其分配给createStore()
方法, 将reducer
作为参数传入。
// ES6 默认参数语法: 将 state 初始化为 5
const reducer = (state = 5) => {
return state;
}
// Redux methods are available from a Redux object
// For example: Redux.createStore()
// Define the store here:
const store = Redux.createStore(reducer)
Redux Store
中获取状态使用
getState()
方法获取Redux store
对象中保存的当前state
使用store.getState()
从store
中获取state
,并将其分配给新的变量currentState
const store = Redux.createStore(
(state = 5) => state
);
// Change code below this line
let currentState = store.getState();
由于 Redux 是一个状态管理框架,因此更新状态是其核心任务之一。 在 Redux 中,所有状态更新都由调度操作(
dispatching actions
)触发。 Action 只是一个 JavaScript 对象,其中包含有关已发生的 action 事件的信息。 Redux store 接收这些 action 对象,然后相应地更新其状态。 有时 Redux action 也会携带一些数据。 例如,该 action 在用户登录后携带用户名。 虽然数据是可选的,但 action 必须带有指定所发生 action 的 'type' 的type
属性。 将 Redux action 视为将有关应用中发生的事件的信息传递到 Redux store 的信使。 然后,store 根据发生的 action 执行更新状态的业务。 编写 Redux action 就像使用类型属性声明对象一样简单。 声明一个对象action
并为其指定一个设置为字符串'LOGIN'
的属性type
。
// Define an action here:
let action = {
type: 'LOGIN'
};
创建 Action 后,下一步是将操作发送到 Redux store,以便它可以更新其状态。 在 Redux 中,您可以定义 Action 创建者来完成此操作。 Action 创建者只是一个返回 Action 的 JavaScript 函数。 换句话说,Action 创建者创建表示 Action 事件的对象。 定义一个名为
actionCreator()
的函数,该函数在调用时返回action
对象。
const action = {
type: 'LOGIN'
}
// Define an action creator here:
function actionCreator() {
return action;
}
dispatch
方法是用于将 Action 调度到 Redux store 的方法。 调用store.dispatch()
并传递从 Action 创建者返回的值会将操作发送回 store。 回想一下,Action 创建者返回一个对象,该对象具有指定已发生的 Action 的类型属性。 然后,该方法将操作对象调度到 Redux 存储区。 根据前面挑战的示例,以下行是等效的,并且都调度类型LOGIN
的 Action:
store.dispatch(actionCreator());
store.dispatch({ type: 'LOGIN' });
代码编辑器中的 Redux store 具有初始化状态, 该状态是包含当前设置为
false
的login
属性的对象。 还有一个名为loginAction()
的 action 创建器,它返回类型LOGIN
的 action。 通过调用dispatch
方法将LOGIN
action 调度到 Redux store, 并传入loginAction()
创建的 action。
const store = Redux.createStore(
(state = {login: false}) => state
);
const loginAction = () => {
return {
type: 'LOGIN'
}
};
// Dispatch the action here:
store.dispatch(loginAction());
创建 action 并 dispatch 后, Redux store 需要知道如何响应 action。 这是
reducer
函数的工作, Redux 中的reducer
负责为响应 action 而发生的状态修改。reducer
将state
和action
作为参数,并且始终返回新的state
。 Redux 中的另一个关键原则是state
是只读的。 换句话说,reducer
函数必须始终返回state
的新副本,并且从不直接修改状态。 Redux 不强制要求状态不可变性,但是,你负责在reducer
函数的代码中强制执行它。
const defaultState = {
login: false
};
const reducer = (state = defaultState, action) => {
// Change code below this line
if (action.type === "LOGIN") {
return {
login: true
};
} else {
return state;
}
// Change code above this line
};
const store = Redux.createStore(reducer);
const loginAction = () => {
return {
type: 'LOGIN'
}
};
const defaultState = {
authenticated: false
};
const authReducer = (state = defaultState, action) => {
// Change code below this line
switch (action.type) {
case "LOGIN":
return {
authenticated: true
};
case "LOGOUT":
return {
authenticated: false
};
default:
return defaultState;
}
// Change code above this line
};
const store = Redux.createStore(authReducer);
const loginUser = () => {
return {
type: 'LOGIN'
}
};
const logoutUser = () => {
return {
type: 'LOGOUT'
}
};
使用 Redux 时的常见做法是将 Action 类型分配为只读常量, 然后在使用这些常量的位置引用这些常量。 可以重构正在使用的代码,以将 Action 类型编写为
const
声明。 将LOGIN
和LOGOUT
声明为const
值, 并分别将它们分配给字符串'LOGIN'
和'LOGOUT'
。 然后,编辑authReducer()
和 Action 创建者以引用这些常量而不是字符串值。 注意:通常约定以 全大写 形式写入常量,这也是 Redux 中的标准做法。
const LOGIN = "LOGIN";
const LOGOUT = "LOGOUT";
const defaultState = {
authenticated: false
};
const authReducer = (state = defaultState, action) => {
switch (action.type) {
case LOGIN:
return {
authenticated: true
}
case LOGOUT:
return {
authenticated: false
}
default:
return state;
}
};
const store = Redux.createStore(authReducer);
const loginUser = () => {
return {
type: LOGIN
}
};
const logoutUser = () => {
return {
type: LOGOUT
}
};
你有权访问 Redux
store
对象的另一个方法是store.subscribe()
。 这允许你将侦听器函数订阅到 store, 每当针对 store dispatch action 时都会调用这些函数。 此方法的一个简单用途是向您的 store 订阅一个函数, 该函数仅在每次收到 action 并更新 store 时记录一条消息。 编写一个回调函数,每次 store 收到 action 时递增全局变量count
, 并将此函数传递给store.subscribe()
方法。 你将看到连续调用store.dispatch()
三次, 每次都直接传入一个 action 对象。 观察 action dispatch 之间的控制台输出,以查看更新的发生情况。
const ADD = 'ADD';
const reducer = (state = 0, action) => {
switch(action.type) {
case ADD:
return state + 1;
default:
return state;
}
};
const store = Redux.createStore(reducer);
// Global count variable:
let count = 0;
// Change code below this line
store.subscribe(() => count++);
// Change code above this line
store.dispatch({type: ADD});
console.log(count);
store.dispatch({type: ADD});
console.log(count);
store.dispatch({type: ADD});
console.log(count);
reducer
当应用的状态开始变得更加复杂时,可能很容易将状态划分为多个部分。 相反,请记住 Redux 的第一个原则: 所有应用状态都保存在 store 中的单个状态对象中。 因此,Redux 提供了
reducer
组合作为复杂状态模型的解决方案。 定义多个reducer
来处理应用程序状态的不同部分, 然后将这些reducer
组合成一个根reducer
(root reducer
)。 然后将根reducer
传递到 ReduxcreateStore()
方法中。 为了让我们将多个reducer
组合在一起,Redux 提供了combineReducers()
方法。 此函数接受对象作为参数,您可以在其中定义将键关联到特定reducer
函数的属性。 例如,在具有用户身份验证的笔记应用中, 一个reducer
可以处理身份验证, 而另一个reducer
可以处理用户正在提交的文本和笔记。
const rootReducer = Redux.combineReducers({
auth: authenticationReducer,
notes: notesReducer
});
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
const counterReducer = (state = 0, action) => {
switch(action.type) {
case INCREMENT:
return state + 1;
case DECREMENT:
return state - 1;
default:
return state;
}
};
const LOGIN = 'LOGIN';
const LOGOUT = 'LOGOUT';
const authReducer = (state = {authenticated: false}, action) => {
switch(action.type) {
case LOGIN:
return {
authenticated: true
}
case LOGOUT:
return {
authenticated: false
}
default:
return state;
}
};
const rootReducer = Redux.combineReducers({
count: counterReducer,
auth: authReducer
}); // Define the root reducer here
const store = Redux.createStore(rootReducer);
const ADD_NOTE = 'ADD_NOTE';
const notesReducer = (state = 'Initial State', action) => {
switch(action.type) {
// Change code below this line
case ADD_NOTE:
return action.text;
// Change code above this line
default:
return state;
}
};
const addNoteText = (note) => {
// Change code below this line
return {
type: ADD_NOTE,
text: note
};
// Change code above this line
};
const store = Redux.createStore(notesReducer);
console.log(store.getState());
store.dispatch(addNoteText('Hello!'));
console.log(store.getState());
const REQUESTING_DATA = 'REQUESTING_DATA'
const RECEIVED_DATA = 'RECEIVED_DATA'
const requestingData = () => { return {type: REQUESTING_DATA} }
const receivedData = (data) => { return {type: RECEIVED_DATA, users: data.users} }
const handleAsync = () => {
return function(dispatch) {
// Dispatch request action here
dispatch(requestingData());
setTimeout(function() {
let data = {
users: ['Jeff', 'William', 'Alice']
}
// Dispatch received data action here
dispatch(receivedData(data));
}, 2500);
}
};
const defaultState = {
fetching: false,
users: []
};
const asyncDataReducer = (state = defaultState, action) => {
switch(action.type) {
case REQUESTING_DATA:
return {
fetching: true,
users: []
}
case RECEIVED_DATA:
return {
fetching: false,
users: action.users
}
default:
return state;
}
};
const store = Redux.createStore(
asyncDataReducer,
Redux.applyMiddleware(ReduxThunk.default)
);
// Define a constant for increment action types
const INCREMENT = "INCREMENT";
// Define a constant for decrement action types
const DECREMENT = "DECREMENT";
// Define the counter reducer which will increment or decrement the state based on the action it receives
const counterReducer = (state = 0, action) => {
switch(action.type) {
case INCREMENT:
return state + 1;
case DECREMENT:
return state - 1;
default:
return state;
}
};
// Define an action creator for incrementing
const incAction = () => {
return {
type: INCREMENT
};
};
// Define an action creator for decrementing
const decAction = () => {
return {
type: DECREMENT
};
};
// Define the Redux store here, passing in your reducers
const store = Redux.createStore(counterReducer);
不可变状态意味着你从不直接修改状态,而是返回一个新的状态副本。 Redux 不会主动在其 store 或 reducer 中强制执行状态不变性, 该责任落在程序员身上。 代码编辑器中有一个
store
和reducer
用于管理待办事项。 完成在reducer
中写入案例ADD_TO_DO
以将新的待办事项附加到状态。 有几种方法可以使用标准 JavaScript 或 ES6 来完成此操作。 看看你是否可以找到一种方法来返回一个新数组, 其中的项目action.todo
附加到末尾。 由于 Redux 中的状态不变性, 此挑战的目标是在 reducer 函数中返回一个新的状态副本。
const ADD_TO_DO = 'ADD_TO_DO';
// A list of strings representing tasks to do:
const todos = [
'Go to the store',
'Clean the house',
'Cook dinner',
'Learn to code',
];
const immutableReducer = (state = todos, action) => {
switch(action.type) {
case ADD_TO_DO:
// Don't mutate state here or the tests will fail
// 拼接, 加在数组最后
// return state.concat(action.todo);
// 或 ES6 展开运算符
return [...state, action.todo]
default:
return state;
}
};
const addToDo = (todo) => {
return {
type: ADD_TO_DO,
todo
}
}
const store = Redux.createStore(immutableReducer);
...
展开运算符有多种应用,其中之一非常适合 从现有数组生成新数组
let newArray = [...myArray];
newArray
现在是克隆myArray
。 两个数组仍然单独存在于内存中。 如果你执行类似newArray.push(5)
,myArray
则不会改变。
const immutableReducer = (state = ['Do not mutate state!'], action) => {
switch(action.type) {
case 'ADD_TO_DO':
// Don't mutate state here or the tests will fail
let newArray = [...state, action.todo];
return newArray;
default:
return state;
}
};
const addToDo = (todo) => {
return {
type: 'ADD_TO_DO',
todo
}
}
const store = Redux.createStore(immutableReducer);
const immutableReducer = (state = [0,1,2,3,4,5], action) => {
switch(action.type) {
case 'REMOVE_ITEM':
// Don't mutate state here or the tests will fail
return [
...state.slice(0, action.index),
...state.slice(action.index + 1, state.length)
];
// 或
// return state.slice(0, action.index).concat(state.slice(action.index + 1, state.length));
// 或
// return state.filter((_, index) => index !== action.index);
default:
return state;
}
};
const removeItem = (index) => {
return {
type: 'REMOVE_ITEM',
index
}
}
const store = Redux.createStore(immutableReducer);
Object.assign
复制一个对象
Object.assign()
获取目标对象和源对象并将属性从源对象映射到目标对象。 任何匹配的属性都会被源对象中的属性覆盖。 此行为通常用于通过传递一个空对象作为第一个参数, 然后传递要复制的对象来制作对象的浅表副本。 这是一个例子:
const newObject = Object.assign({}, obj1, obj2);
const defaultState = {
user: 'CamperBot',
status: 'offline',
friends: '732,982',
community: 'freeCodeCamp'
};
const immutableReducer = (state = defaultState, action) => {
switch(action.type) {
case 'ONLINE':
// Don't mutate state here or the tests will fail
return Object.assign({}, state, { status: "online" });
default:
return state;
}
};
const wakeUp = () => {
return {
type: 'ONLINE'
}
};
const store = Redux.createStore(immutableReducer);
class DisplayMessages extends React.Component {
// Change code below this line
constructor(props) {
super(props);
this.state = {
input: "",
messages: []
};
}
// Change code above this line
render() {
return <div />
}
};
class DisplayMessages extends React.Component {
constructor(props) {
super(props);
this.state = {
input: '',
messages: []
};
this.handleChange = this.handleChange.bind(this);
this.submitMessage = this.submitMessage.bind(this);
}
// Add handleChange() and submitMessage() methods here
handleChange(event) {
// 无需更新 messages, 会自动合并更新, 不会导致 messages 丢失
this.setState({
input: event.target.value
});
}
submitMessage() {
this.setState(state => ({
input: "",
messages: [...state.messages, state.input]
}));
}
render() {
return (
<div>
<h2>Type in a new Message:</h2>
{ /* Render an input, button, and ul below this line */ }
<input onChange={this.handleChange} value={this.state.input} />
<button onClick={this.submitMessage}>Add message</button>
<ul>
{
this.state.messages.map((m, i) =>
(
<li key={i}>{m}</li>
)
)
}
</ul>
{ /* Change code above this line */ }
</div>
);
}
};
// Define ADD, addMessage(), messageReducer(), and store here:
const ADD = "ADD";
const addMessage = message => {
return {
type: ADD,
message
};
};
// Use ES6 default paramter to give the 'previousState' parameter an initial value.
const messageReducer = (previousState = [], action) => {
// Use switch statement to lay out the reducer logic in response to different action type
switch (action.type) {
case ADD:
// Use ES6 spread operator to return a new array where the new message is added to previousState
return [...previousState, action.message];
default:
// A default case to fall back on in case if the update to Redux store is not for this specific state.
return previousState;
}
};
const store = Redux.createStore(messageReducer);
下一步是提供对 Redux store 的 React 访问以及调度(dispatch)更新所需的 action。 React Redux 提供了它的
react-redux
包来帮助完成这些任务。 React Redux 提供了一个具有两个关键特性的小型 API:Provider
和connect
。Provider
需要两个 props,即 Redux store 和应用的子组件。 为 App 组件定义Provider
可能如下所示:
<Provider store={store}>
<App/>
</Provider>
// Redux:
const ADD = 'ADD';
const addMessage = (message) => {
return {
type: ADD,
message
}
};
const messageReducer = (state = [], action) => {
switch (action.type) {
case ADD:
return [
...state,
action.message
];
default:
return state;
}
};
const store = Redux.createStore(messageReducer);
// React:
class DisplayMessages extends React.Component {
constructor(props) {
super(props);
this.state = {
input: '',
messages: []
}
this.handleChange = this.handleChange.bind(this);
this.submitMessage = this.submitMessage.bind(this);
}
handleChange(event) {
this.setState({
input: event.target.value
});
}
submitMessage() {
this.setState((state) => {
const currentMessage = state.input;
return {
input: '',
messages: state.messages.concat(currentMessage)
};
});
}
render() {
return (
<div>
<h2>Type in a new Message:</h2>
<input
value={this.state.input}
onChange={this.handleChange}/><br/>
<button onClick={this.submitMessage}>Submit</button>
<ul>
{this.state.messages.map( (message, idx) => {
return (
<li key={idx}>{message}</li>
)
})
}
</ul>
</div>
);
}
};
const Provider = ReactRedux.Provider;
class AppWrapper extends React.Component {
// Render the Provider below this line
render() {
return (
<Provider store={store}>
<DisplayMessages />
</Provider>
);
}
// Change code above this line
};
Provider
组件允许您为 React 组件提供state
和dispatch
, 但你必须准确指定所需的 state 和 action。 这样,您可以确保每个组件只能访问它所需的 state。 你可以通过创建两个函数来实现此目的:mapStateToProps()
和mapDispatchToProps()
。
const state = [];
// Change code below this line
const mapStateToProps = (state) => {
return {
messages: state
};
};
mapDispatchToProps()
函数用于为你的 React 组件提供特定的操作(action)创建者, 以便它们可以针对 Redux store 调度(dispatch) 操作(action)。 一个示例
{
submitLoginUser: function(username) {
dispatch(loginUser(username));
}
}
const addMessage = (message) => {
return {
type: 'ADD',
message: message
}
};
// Change code below this line
const mapDispatchToProps = (dispatch) => {
return {
submitNewMessage: (message) => {
dispatch(addMessage(message));
}
};
};
connect(mapStateToProps, mapDispatchToProps)(MyComponent)
const addMessage = (message) => {
return {
type: 'ADD',
message: message
}
};
const mapStateToProps = (state) => {
return {
messages: state
}
};
const mapDispatchToProps = (dispatch) => {
return {
submitNewMessage: (message) => {
dispatch(addMessage(message));
}
}
};
class Presentational extends React.Component {
constructor(props) {
super(props);
}
render() {
return <h3>This is a Presentational Component</h3>
}
};
const connect = ReactRedux.connect;
// Change code below this line
const ConnectedComponent = connect(mapStateToProps, mapDispatchToProps)(Presentational);
// Redux:
const ADD = 'ADD';
const addMessage = (message) => {
return {
type: ADD,
message: message
}
};
const messageReducer = (state = [], action) => {
switch (action.type) {
case ADD:
return [
...state,
action.message
];
default:
return state;
}
};
const store = Redux.createStore(messageReducer);
// React:
class Presentational extends React.Component {
constructor(props) {
super(props);
this.state = {
input: '',
messages: []
}
this.handleChange = this.handleChange.bind(this);
this.submitMessage = this.submitMessage.bind(this);
}
handleChange(event) {
this.setState({
input: event.target.value
});
}
submitMessage() {
this.setState((state) => {
const currentMessage = state.input;
return {
input: '',
messages: state.messages.concat(currentMessage)
};
});
}
render() {
return (
<div>
<h2>Type in a new Message:</h2>
<input
value={this.state.input}
onChange={this.handleChange}/><br/>
<button onClick={this.submitMessage}>Submit</button>
<ul>
{this.state.messages.map( (message, idx) => {
return (
<li key={idx}>{message}</li>
)
})
}
</ul>
</div>
);
}
};
// React-Redux:
const mapStateToProps = (state) => {
return { messages: state }
};
const mapDispatchToProps = (dispatch) => {
return {
submitNewMessage: (newMessage) => {
dispatch(addMessage(newMessage))
}
}
};
const Provider = ReactRedux.Provider;
const connect = ReactRedux.connect;
// Define the Container component here:
const Container = connect(mapStateToProps, mapDispatchToProps)(Presentational);
class AppWrapper extends React.Component {
constructor(props) {
super(props);
}
render() {
// Complete the return statement:
return (
<Provider store={store}>
<Container />
</Provider>
);
}
};
现在 Redux 已连接, 你需要将状态管理从
Presentational
组件中提取到 Redux 中。 目前,你已连接 Redux, 但你正在Presentational
组件中本地处理状态。 在Presentational
组件中, 首先删除本地state
中的messages
属性。 这些消息将由 Redux 管理。 接下来,修改submitMessage()
方法, 使其从this.props
调度submitNewMessage()
, 并将来自本地state
的当前消息输入作为参数传入。 由于你从本地状态中删除了messages
, 因此也在此处从对this.setState()
的调用中删除了messages
属性。 最后,修改render()
方法, 使其映射从props
而不是state
接收的消息。 进行这些更改后,应用将继续正常运行,但 Redux 管理状态除外。 此示例还说明了组件如何具有本地state
: 你的组件仍然在其自己的state
中本地跟踪用户输入。 你可以看到 Redux 如何在 React 之上提供一个有用的状态管理框架。 一开始,你只使用 React 的本地状态就获得了相同的结果, 这通常可以通过简单的应用程序来实现。 但是,随着你的应用程序变得更大、更复杂, 你的状态管理也会变得更复杂,这就是 Redux 解决的问题。 本地 state 管理input
, 其它交给 Redux (message
存到Redux store
), 连接 React 与 Redux: 1. 将 Redux state 映射到 React 的 props 中Redux state 存储数据 React 从 props 中访问 Redux 存储的状态数据将 Redux dispatch 映射到 React 的 props 中Redux dispatch 更新状态数据 React 从 props 中取出来更新 Redux 管理的状态数据
// Redux:
const ADD = 'ADD';
const addMessage = (message) => {
return {
type: ADD,
message: message
}
};
const messageReducer = (state = [], action) => {
switch (action.type) {
case ADD:
return [
...state,
action.message
];
default:
return state;
}
};
const store = Redux.createStore(messageReducer);
// React:
const Provider = ReactRedux.Provider;
const connect = ReactRedux.connect;
// Change code below this line
class Presentational extends React.Component {
constructor(props) {
super(props);
// Remove property 'messages' from Presentational's local state
this.state = {
input: '',
// messages: []
}
this.handleChange = this.handleChange.bind(this);
this.submitMessage = this.submitMessage.bind(this);
}
handleChange(event) {
this.setState({
input: event.target.value
});
}
submitMessage() {
// Call 'submitNewMessage', which has been mapped to Presentational's props, with a new message;
// meanwhile, remove the 'messages' property from the object returned by this.setState().
this.props.submitNewMessage(this.state.input);
this.setState({
input: ''
});
}
render() {
return (
<div>
<h2>Type in a new Message:</h2>
<input
value={this.state.input}
onChange={this.handleChange}/><br/>
<button onClick={this.submitMessage}>Submit</button>
<ul>
{/* The messages state is mapped to Presentational's props; therefore, when rendering,
you should access the messages state through props, instead of Presentational's
local state. */}
{this.props.messages.map( (message, idx) => {
return (
<li key={idx}>{message}</li>
)
})
}
</ul>
</div>
);
}
};
// Change code above this line
const mapStateToProps = (state) => {
return {messages: state}
};
const mapDispatchToProps = (dispatch) => {
return {
submitNewMessage: (message) => {
dispatch(addMessage(message))
}
}
};
const Container = connect(mapStateToProps, mapDispatchToProps)(Presentational);
class AppWrapper extends React.Component {
render() {
return (
<Provider store={store}>
<Container/>
</Provider>
);
}
};
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider, connect } from 'react-redux'
import { createStore, combineReducers, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from './redux/reducers'
import App from './components/App'
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
);
// Only change code below this line
console.log("Now I know React and Redux!");
参考:
参考:
参考:
// 注释内容
在 Sass 中,这种注释方式在 编译后不会保留下来。
/* 注释内容 */
在 Sass 中,这种注释方式在 编译后会保留下来。 因为这种注释方式跟 CSS 注释方式是相同的,所以编译后会保留下来。
/*! 注释内容 */
我们都知道压缩工具会删除所有的注释, 有些时候为了保留一些版权声明的注释说明,可以采用以下方式:
/*! 注释内容 */
也就是说在注释内容前面加上一个
!
,这种压缩工具就不会删除这条注释信息了。 不过这种注释方式用得很少,一般在 CSS 文件顶部为了声明版权信息才会使用。 Sass
/*! Copyright ©2023-present yiyun, All Rights Reserved */
$height: 20px;
body {
height: $height;
line-height: $height;
}
编译出来的 CSS 代码如下: CSS
/*! Copyright ©2023-present yiyun, All Rights Reserved */
body {
height: 20px;
line-height: 20px;
}
感谢帮助!
传统 CSS 中, 通常,每个元素都针对不同的行来设置其样式,如下所示:
nav {
background-color: red;
}
nav ul {
list-style: none;
}
nav ul li {
display: inline-block;
}
对于大型项目,CSS 文件将有许多行和规则。 这就是嵌套可以通过在相应的父元素中放置子样式规则来帮助组织代码的地方:
nav {
background-color: red;
ul {
list-style: none;
li {
display: inline-block;
}
}
}
实践
<style type='text/scss'>
.blog-post {
h1 {
text-align: center;
color: blue;
}
p {
font-size: 20px;
}
}
</style>
<div class="blog-post">
<h1>Blog Title</h1>
<p>This is a paragraph</p>
</div>
在 Sass 中,mixin 是一组可以在整个样式表中重用的 CSS 声明。 较新的 CSS 功能需要时间才能完全采用并准备好在所有浏览器中使用。 随着功能添加到浏览器中,使用它们的 CSS 规则可能需要供应商前缀。 考虑
box-shadow
:
div {
-webkit-box-shadow: 0px 0px 4px #fff;
-moz-box-shadow: 0px 0px 4px #fff;
-ms-box-shadow: 0px 0px 4px #fff;
box-shadow: 0px 0px 4px #fff;
}
为所有具有
box-shadow
的元素重写此规则或更改每个值以测试不同的效果需要大量键入。 Mixins 就像 CSS 的函数。 如下所示:
@mixin box-shadow($x, $y, $blur, $c){
-webkit-box-shadow: $x $y $blur $c;
-moz-box-shadow: $x $y $blur $c;
-ms-box-shadow: $x $y $blur $c;
box-shadow: $x $y $blur $c;
}
定义以
@mixin
开头,后跟自定义名称。 参数(上例中的$x
、$y
、$blur
和$c
)是可选的。 现在,每当需要box-shadow
规则时, 只需一行调用 mixin 即可取代键入所有 vendor 前缀的行。 使用@include
指令调用mixin
:
div {
@include box-shadow(0px, 0px, 4px, #fff);
}
实践 为
border-radius
编写一个mixin
并为其指定一个$radius
参数。 它应使用示例中的所有 vendor 前缀。 然后使用border-radius
mixin 使#awesome
元素的边框半径为15px
。
<style type='text/scss'>
@mixin border-radius($radius) {
-webkit-border-radius: $radius;
-moz-border-radius: $radius;
-ms-border-radius: $radius;
border-radius: $radius;
}
#awesome {
width: 150px;
height: 150px;
background-color: green;
@include border-radius(15px);
}
</style>
<div id="awesome"></div>
/* 定义函数 */
@mixin custom-mixin-name($param1, $param2, ....) {
// CSS Properties Here...
}
/* 使用函数 */
element {
@include custom-mixin-name(value1, value2, ....);
}
一个官网示例: Sass: @mixin and @include 可选参数 CSS
.mail-icon {
text-indent: -99999em;
overflow: hidden;
text-align: left;
background-image: url("/images/mail.svg");
background-repeat: no-repeat;
background-position: 0 50%;
}
SCSS
@mixin replace-text($image, $x: 50%, $y: 50%) {
text-indent: -99999em;
overflow: hidden;
text-align: left;
background: {
image: $image;
repeat: no-repeat;
position: $x $y;
}
}
.mail-icon {
@include replace-text(url("/images/mail.svg"), 0);
}
Sass
@mixin replace-text($image, $x: 50%, $y: 50%)
text-indent: -99999em
overflow: hidden
text-align: left
background:
image: $image
repeat: no-repeat
position: $x $y
.mail-icon
@include replace-text(url("/images/mail.svg"), 0)
@if
和 @else
为样式添加逻辑Sass 中的
@if
指令对于测试特定情况很有用 它的工作方式就像 JavaScript 中的if
语句一样。
@mixin make-bold($bool) {
@if $bool == true {
font-weight: bold;
}
}
就像在 JavaScript 中一样,
@else if
和@else
指令测试更多条件:
@mixin text-effect($val) {
@if $val == danger {
color: red;
}
@else if $val == alert {
color: yellow;
}
@else if $val == success {
color: green;
}
@else {
color: black;
}
}
实践 创建一个名为
border-stroke
的 mixin,该 mixin 采用参数$val
。 mixin 应使用@if
、@else if
和@else
指令检查以下条件:
light - 1px solid black
medium - 3px solid black
heavy - 6px solid black
如果
$val
参数值不是light
、medium
或heavy
, 则border
属性应设置为none
。
<style type='text/scss'>
@mixin border-stroke($val) {
@if $val == light {
border: 1px solid black;
}
@else if $val == medium {
border: 3px solid black;
}
@else if $val == heavy {
border: 6px solid black;
}
@else {
border: none;
}
}
#box {
width: 150px;
height: 150px;
background-color: red;
@include border-stroke(medium);
}
</style>
<div id="box"></div>
@for
创建 Sass 循环
@for
有两种方式:
from A through B
[A, B]
from A to B
[A, b)
@for $i from 1 through 3 {
// some CSS
}
// 1 2 3
@for $i from 1 to 3 {
// some CSS
}
// 1 2
PS:可以直接带上单位 (
%, px 等
) 进行计算
@for $i from 1 through 12 {
.col-#{$i} { width: 100%/12 * $i; }
}
@for $i from 1 to 12 {
.col-#{$i} { width: 100%/12 * $i; }
}
该
#{$i}
部分是将变量 (i
) 与文本组合以生成字符串 的语法。 当 Sass 文件转换为 CSS 时,它看起来像这样:
.col-1 {
width: 8.33333%;
}
.col-2 {
width: 16.66667%;
}
...
.col-12 {
width: 100%;
}
这是一个创建
grid
布局的好方法, 实践 使用@for
指令用$j
从1
到6
(不包括6
), 创建 5 个类,从.text-1
到.text-5
, 每个都有font-size
值为15px*$j
<style type='text/scss'>
@for $j from 1 to 6 {
.text-#{$j} {
font-size: 15px * $j;
}
}
</style>
<p class="text-1">Hello</p>
<p class="text-2">Hello</p>
<p class="text-3">Hello</p>
<p class="text-4">Hello</p>
<p class="text-5">Hello</p>
@each
映射列表中的项Sass 还提供了
@each
循环遍历列表 或 映射中每个项 的指令。 在每次迭代中,变量被分配给列表或映射中的当前值。
@each $color in blue, red, green {
.#{$color}-text {color: $color;}
}
映射 的语法略有不同。 一个例子:
$colors: (color1: blue, color2: red, color3: green);
@each $key, $color in $colors {
.#{$color}-text {color: $color;}
}
请注意,需要该
$key
变量来引用映射中的键。 否则,编译后的 CSS 将包含color1
,color2
...。 以上两个代码示例都转换为以下 CSS:
.blue-text {
color: blue;
}
.red-text {
color: red;
}
.green-text {
color: green;
}
实践 编写一个
@each
遍历列表的指令:blue, black, red
并将每个变量分配给一个.color-bg
类, 其中color
每个项目的部分都会发生变化。 每个类都应该设置background-color
各自的颜色。
<style type='text/scss'>
$colors: blue, black, red;
@each $color in $colors {
.#{$color}-bg {
background-color: $color;
}
}
div {
height: 200px;
width: 200px;
}
</style>
<div class="blue-bg"></div>
<div class="black-bg"></div>
<div class="red-bg"></div>
@while
该
@while
指令是一个选项,具有与 JavaScriptwhile
循环类似的功能。 它会创建 CSS 规则,直到满足条件为止。 简单网格系统
$x: 1;
@while $x < 13 {
.col-#{$x} { width: 100%/12 * $x;}
$x: $x + 1;
}
首先,定义一个变量
$x
并将其设置为 1。 接下来,使用@while
指令创建网格系统while
$x
小于 13。 在为 设置 CSS 规则后width
,$x
递增 1 以避免无限循环。 实践 用于@while
创建一系列具有不同font-sizes
.text-1
到text-5
。 然后设置font-size
为15px
乘以当前索引号。 确保避免无限循环!
<style type='text/scss'>
$x: 1;
@while $x <= 5 {
.text-#{$x} {
font-size: 15px * $x;
}
$x: $x + 1;
}
</style>
<p class="text-1">Hello</p>
<p class="text-2">Hello</p>
<p class="text-3">Hello</p>
<p class="text-4">Hello</p>
<p class="text-5">Hello</p>
Sass 中的 Partials 是包含 CSS 代码段的单独文件。 这些被导入并在其他 Sass 文件中使用。 这是将类似代码分组到一个模块中以保持其组织性的好方法。 partials 的名称以下划线 (
_
) 字符开头,告诉 Sass 这是 CSS 的一小段,不要将其转换为 CSS 文件。 此外,Sass 文件以文件扩展名结尾.scss
。 要将部分代码放入另一个 Sass 文件,请使用该@import
指令。 例如, 如果你所有的 mixins 都保存在名为“_mixins.scss”
的部分中, 并且在“main.scss”
文件中需要它们, 那么在主文件中使用它们的方法如下:
@import 'mixins'
请注意,语句中不需要下划线和文件扩展名
import
Sass 理解它是 partials 的。 一旦将 partials 导入到文件中,所有变量、mixin 和其他代码都可以使用。 实践 编写一条@import
语句,将 partial named 导入_variables.scss
到main.scss
文件中。
<!-- The main.scss file -->
@import 'variables'
@extend
**) 到另一个元素个人理解: @extend 更像是继承 Sass 有一个特性
extend
,可以很容易地从一个元素借用 CSS 规则并在另一个元素的基础上构建。 例如, 下面的 CSS 规则块为一个.panel
类设置样式。 它有一个background-color
,height
和border
.panel {
background-color: red;
height: 70px;
border: 2px solid green;
}
现在您想要另一个的面板名为
.big-panel
。 它具有与 相同的基本属性.panel
,但还需要一个width
和font-size
。 可以从 复制和粘贴初始 CSS 规则.panel
,但是随着您添加更多类型的面板,代码会变得重复。 该extend
指令是重用为一个元素编写的规则,然后为另一个元素添加更多规则的简单方法:
.big-panel {
@extend .panel;
width: 150px;
font-size: 2em;
}
除了新样式之外,
.big-panel
与.panel
具有相同的属性 实践 创建一个 CSS 类名为.info-important
, 扩展自.info
, 并有background-color: magenta
<style type='text/scss'>
h3{
text-align: center;
}
.info{
width: 200px;
border: 1px solid black;
margin: 0 auto;
}
/* 使用 @extend */
.info-important {
@extend .info;
background-color: magenta;
}
</style>
<h3>Posts</h3>
<div class="info-important">
<p>This is an important post. It should extend the class ".info" and have its own CSS styles.</p>
</div>
<div class="info">
<p>This is a simple post. It has basic styling and can be extended for other uses.</p>
</div>
{ /* 我是注释 */ }
要将注释放在 JSX 中,请使用语法 {/* */}
环绕注释文本。
const JSX = (
<div>
<h1>This is a block of JSX</h1>
<p>Here's a subtitle</p>
{ /* 我是 JSX 中的注释 */ }
</div>
);
在 JSX 中, 任何 JSX 元素都可以使用自闭合标记编写,并且每个元素都必须闭合。 例如,换行标记必须始终写为
<br />
,才能成为可以转译的有效 JSX。 而在 HTML 中, 例如,换行标记可以写为<br>
或<br />
,但绝不应写为<br></br>
,因为它不包含任何内容。
有两种方法可以创建 React 组件。第一种方法是使用 JavaScript 函数。 以这种方式定义组件会创建一个无状态的功能组件。 现在,将无状态组件视为可以接收数据并呈现数据,但不管理或跟踪对该数据的更改的组件。
要创建带有函数的组件,您只需编写一个返回 JSX 或 null
的 JavaScript 函数。需要注意的重要一点是,React 要求你的函数名称以大写字母开头。
const DemoComponent = function() {
return (
<div className='customClass' />
);
};
定义 React 组件的另一种方法是使用 ES6 class
语法。在以下示例中, Kitten
扩展 React.Component
:
class Kitten extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<h1>Hi</h1>
);
}
}
props
值: MyComponent.defaultProps
默认 props
值
MyComponent.defaultProps = { location: 'San Francisco' }
定义 location
默认值为 'San Francisco'
,
React 分配默认值为: undefined
, 但若指定分配 null
, 则会保持 null
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() {
{ /* Change code below this line */ }
return <Items quantity={10} />
{ /* Change code above this line */ }
}
};
propTypes
限制 props
通过
propTypes
限制props
参考:Typechecking With PropTypes – React
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
};
this.props
访问 props
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
{ /* Change code below this line */ }
<Welcome name="world"/>
{ /* Change code above this line */ }
</div>
);
}
};
class Welcome extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
{ /* Change code below this line */ }
<p>Hello, <strong>{this.props.name}</strong>!</p>
{ /* Change code above this line */ }
</div>
);
}
};
propTypes, defaultProps
const Camper = props => <p>{props.name}</p>;
Camper.propTypes = {
name: PropTypes.string.isRequired
};
Camper.defaultProps = {
name: "CamperBot"
};
class CampSite extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Camper name="test"/>
</div>
);
}
};
ReactDOM.render(<CampSite />, document.getElementById("challenge-node"));
class StatefulComponent extends React.Component {
constructor(props) {
super(props);
// Only change code below this line
this.state = {
firstName: "Hello"
};
// Only change code above this line
}
render() {
return (
<div>
<h1>{this.state.firstName}</h1>
</div>
);
}
};
setState
更新 state
this.setState({
username: 'Lewis'
});
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
text: "Hello"
};
// Change code below this line
this.handleClick = this.handleClick.bind(this);
// Change code above this line
}
handleClick() {
this.setState({
text: "You clicked!"
});
}
render() {
return (
<div>
{ /* Change code below this line */ }
<button onClick={this.handleClick}>Click Me</button>
{ /* Change code above this line */ }
<h1>{this.state.text}</h1>
</div>
);
}
};
有时候,需要访问之前的
state
用于更新现在的state
, 然而,state
更新可能是异步的,这意味着 React 可能批量多个setState()
呼叫在单个更新,这意味着你不能依赖于之前的值:this.state
或this.props
当用于计算下一个值时, 因此你不能下面这样写:
// 错误 示范
this.setState({
counter: this.state.counter + this.props.increment
});
// 正确 示范
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
也可以如下
this.setState(state => ({
counter: state.counter + 1
}));
注意:必须将函数返回值(对象)包裹在
()
中
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
visibility: false
};
// Change code below this line
this.toggleVisibility = this.toggleVisibility.bind(this);
// Change code above this line
}
// Change code below this line
toggleVisibility() {
this.setState(state => ({
visibility: !state.visibility
}));
}
// Change code above this line
render() {
if (this.state.visibility) {
return (
<div>
<button onClick={this.toggleVisibility}>Click Me</button>
<h1>Now you see me!</h1>
</div>
);
} else {
return (
<div>
<button onClick={this.toggleVisibility}>Click Me</button>
</div>
);
}
}
}
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
// Change code below this line
this.increment = this.increment.bind(this);
this.decrement = this.decrement.bind(this);
this.reset = this.reset.bind(this);
// Change code above this line
}
// Change code below this line
increment() {
this.setState(state => ({
count: state.count + 1
}));
}
decrement() {
this.setState(state => ({
count: state.count - 1
}));
}
reset() {
this.setState(state => ({
count: 0
}))
}
// Change code above this line
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>
);
}
};
input
class ControlledInput extends React.Component {
constructor(props) {
super(props);
this.state = {
input: ''
};
// Change code below this line
this.handleChange = this.handleChange.bind(this);
// Change code above this line
}
// Change code below this line
handleChange(event) {
this.setState(state => ({
input: event.target.value
}));
}
// Change code above this line
render() {
return (
<div>
{ /* Change code below this line */}
<input value={this.state.input} onChange={this.handleChange} />
{ /* Change code above this line */}
<h4>Controlled Input:</h4>
<p>{this.state.input}</p>
</div>
);
}
};
Form
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) {
// Change code below this line
event.preventDefault();
this.setState(state => ({
submit: state.input
}));
// Change code above this line
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
{/* Change code below this line */}
<input value={this.state.input} onChange={this.handleChange} />
{/* Change code above this line */}
<button type='submit'>Submit!</button>
</form>
{/* Change code below this line */}
<h1>{this.state.submit}</h1>
{/* Change code above this line */}
</div>
);
}
}
class MyApp extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'CamperBot'
}
}
render() {
return (
<div>
{/* Change code below this line */}
<Navbar name={this.state.name} />
{/* Change code above this line */}
</div>
);
}
};
class Navbar extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
{/* Change code below this line */}
<h1>Hello, my name is: {this.props.name}</h1>
{/* Change code above this line */}
</div>
);
}
};
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>
{ /* Change code below this line */ }
<GetInput input={this.state.inputValue} handleChange={this.handleChange} />
<RenderInput input={this.state.inputValue} />
{ /* Change code above this line */ }
</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>
);
}
};
componentWillMount
Note: The
componentWillMount
Lifecycle method will be deprecated in a future version of 16.X and removed in version 17. Learn more in this article
componentWillMount()
在 render()
之前被调用
componentDidMount
组件被挂载到 DOM 后
componentDidMount()
被调用, 任何对setState()
都会触发组件的重新渲染, 可以在这里获取异步 API 的数据
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
activeUsers: null
};
}
componentDidMount() {
setTimeout(() => {
this.setState({
activeUsers: 1273
});
}, 2500);
}
render() {
return (
<div>
{/* Change code below this line */}
<h1>Active Users: {this.state.activeUsers}</h1>
{/* Change code above this line */}
</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((state) => ({
message: 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
优化重新渲染此方法是优化性能的有用方法。 例如,默认行为是组件在收到新的
props
时重新渲染,即使props
没有更改也是如此。 你可以使用shouldComponentUpdate()
通过比较props
来防止这种情况。 该方法必须返回一个boolean
值,告诉 React 是否更新组件。 你可以将当前 props(this.props
)与下一个props (nextProps
) 进行比较, 以确定是否需要更新,并相应地返回true
或false
。 使OnlyEvens
仅在其新props
的value
为偶数时才更新
class OnlyEvens extends React.Component {
constructor(props) {
super(props);
}
shouldComponentUpdate(nextProps, nextState) {
console.log('Should I update?');
// Change code below this line
if (nextProps.value %2 == 0) {
return true;
}
return false;
// Change code above this line
}
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(state => ({
value: state.value + 1
}));
}
render() {
return (
<div>
<button onClick={this.addValue}>Add</button>
<OnlyEvens value={this.state.value} />
</div>
);
}
}
不会再显示奇数, 点击 2 次, 从 20 到 22
Inline Styles
**)HTML 中内联样式的示例
<div style="color: yellow; font-size: 16px">Mellow Yellow</div>
JSX 元素使用
style
属性,但由于 JSX 的转译方式,你无法将该值设置为string
。 相反,你把它设置为等于JavaScriptobject
。
<div style={{color: "yellow", fontSize: 16}}>Mellow Yellow</div>
请注意,你可以选择将字体大小设置为数字,省略单位
px
,或将其写为72px
。
// Change code above this line
// 需要写在外部
const styles = {
color: 'purple',
fontSize: 40,
border: "2px solid purple",
};
class Colorful extends React.Component {
render() {
// Change code below this line
// 写在这里: undefined, 无法在下面找到, 说明下面 JSX 作用域并不在此方法内, 但很奇怪, 在后面的例子中, answer 又可以直接使用
// const styles = {
// color: 'purple',
// fontSize: 40,
// border: "2px solid purple",
// };
return (
<div style={styles}>Style Me!</div>
);
// Change code above this line
}
};
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',
'It is decidedly so',
'Without a doubt',
'Yes, definitely',
'You may rely on it',
'As I see it, yes',
'Outlook good',
'Yes',
'Signs point to yes',
'Reply hazy try again',
'Ask again later',
'Better not tell you now',
'Cannot predict now',
'Concentrate and ask again',
"Don't count on it",
'My reply is no',
'My sources say no',
'Most likely',
'Outlook not so good',
'Very doubtful'
];
const answer = possibleAnswers[this.state.randomIndex]; // Change this line
// 很奇怪, 为什么这里又可以直接使用 answer
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>
{/* Change code below this line */}
{answer}
{/* Change code above this line */}
</p>
</div>
);
}
}
if-else
条件渲染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
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>
)
}
}
};
&&
简化条件{condition && <p>markup</p>}
如果
condition
为true
,则将返回标记。 如果条件为false
,则操作将在计算condition
后立即返回false
,并且不返回任何内容。
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>
);
}
};
三元表达式
进行条件渲染condition ? expressionIfTrue : expressionIfFalse;
const inputStyle = {
width: 235,
margin: 5
};
class CheckUserAge extends React.Component {
constructor(props) {
super(props);
// Change code below this line
this.state = {
input: '',
userAge: ''
};
// Change code above this line
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 />
{/* Change code below this line */}
{ /* 注意: 可以直接使用 buttonOne, 而不是 <buttonOne /> */ }
{
this.state.userAge === ''
? buttonOne
: this.state.userAge >= 18
? buttonTwo
: buttonThree
}
{/* Change code above this line */}
</div>
);
}
}
props
有条件地渲染class Results extends React.Component {
constructor(props) {
super(props);
}
render() {
{/* Change code below this line */}
return (
<h1>
{this.props.fiftyFifty ? "You Win!" : "You Lose!"}
</h1>
);
{/* Change code above this line */}
}
}
class GameOfChance extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 1
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => {
// Complete the return statement:
return {
counter: prevState.counter + 1
}
});
}
render() {
const expression = Math.random() >= 0.5; // Change this line
return (
<div>
<button onClick={this.handleClick}>Play Again</button>
{/* Change code below this line */}
<Results fiftyFifty={expression} />
{/* Change code above this line */}
<p>{'Turn: ' + this.state.counter}</p>
</div>
);
}
}
输入框中键入的文本超过 15 个字符,则将此边框设置为红色样式。
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
inputStyle = this.state.input.length > 15 ? { border: '3px solid red' } : inputStyle;
// 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(item => <li>{item}</li>); // Change this line
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>
);
}
}
key
属性创建元素数组时,每个元素都需要将
key
属性设置为唯一值。 React 使用这些键来跟踪添加、更改或删除了哪些项。 这有助于在以任何方式修改列表时使重新渲染过程更高效。 注意:key
只需要在同级元素之间是唯一的,它们在应用程序中不需要全局唯一。 通常,你希望使key
能唯一标识正在呈现的元素。 作为最后的手段,可以使用数组索引,但通常应尝试使用唯一标识。
const frontEndFrameworks = [
'React',
'Angular',
'Ember',
'Knockout',
'Backbone',
'Vue'
];
function Frameworks() {
const renderFrameworks = frontEndFrameworks.map((item, index) =>
<li key={index}>{item}</li>
); // Change this line
return (
<div>
<h1>Popular Front End JavaScript Frameworks</h1>
<ul>
{renderFrameworks}
</ul>
</div>
);
};
Array.filter()
动态过滤数组// 取出 users 中所有在线用户:
// online: Boolean
let onlineUsers = users.filter(user => user.online);
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(user => user.online); // Change this line
const renderOnline = usersOnline.map((item, index) => <li key={index}>{item.username}</li>); // Change this line
return (
<div>
<h1>Current Online Users:</h1>
<ul>{renderOnline}</ul>
</div>
);
}
}
renderToString
在服务端渲染 React至此, 渲染 React 组件都是在客户端(客户浏览器), 通常来说你完全可以这么做, 然而,有些情况需要渲染 React 组件在服务端, 因为 React 是一个 JavaScript 视图库,并且你可以使用 Node.js 在服务端运行 JavaScript, 事实上, React 提供了
renderToString()
方法用于此种情况。
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div/>
}
};
// Change code below this line
ReactDOMServer.renderToString(<App />);
Redux Store
Redux
是一个状态管理框架,可以与许多不同的 Web 技术一起使用,包括 React 在 Redux 中,有一个状态对象负责应用程序的整个状态。 这意味着, 如果你有一个包含十个组件的 React 应用程序, 并且每个组件都有自己的本地状态, 则应用程序的整个状态将由 Reduxstore
中的单个状态对象定义。 这也意味着, 每当应用的任何部分想要更新状态时, 它都必须通过Redux store
来实现。 通过单向数据流,可以更轻松地跟踪应用中的状态管理。 Reduxstore
是保存和管理应用程序state
的对象。 Redux 对象上有一个名为createStore()
的方法,用于创建 Reduxstore
。 此方法将reducer
函数作为必需参数。reducer
函数将在后面的挑战中介绍,并且已经在代码编辑器中为您定义。 它只是将state
作为参数并返回state
。 声明一个store
变量并将其分配给createStore()
方法, 将reducer
作为参数传入。
// ES6 默认参数语法: 将 state 初始化为 5
const reducer = (state = 5) => {
return state;
}
// Redux methods are available from a Redux object
// For example: Redux.createStore()
// Define the store here:
const store = Redux.createStore(reducer)
Redux Store
中获取状态使用
getState()
方法获取Redux store
对象中保存的当前state
使用store.getState()
从store
中获取state
,并将其分配给新的变量currentState
const store = Redux.createStore(
(state = 5) => state
);
// Change code below this line
let currentState = store.getState();
由于 Redux 是一个状态管理框架,因此更新状态是其核心任务之一。 在 Redux 中,所有状态更新都由调度操作(
dispatching actions
)触发。 Action 只是一个 JavaScript 对象,其中包含有关已发生的 action 事件的信息。 Redux store 接收这些 action 对象,然后相应地更新其状态。 有时 Redux action 也会携带一些数据。 例如,该 action 在用户登录后携带用户名。 虽然数据是可选的,但 action 必须带有指定所发生 action 的 'type' 的type
属性。 将 Redux action 视为将有关应用中发生的事件的信息传递到 Redux store 的信使。 然后,store 根据发生的 action 执行更新状态的业务。 编写 Redux action 就像使用类型属性声明对象一样简单。 声明一个对象action
并为其指定一个设置为字符串'LOGIN'
的属性type
。
// Define an action here:
let action = {
type: 'LOGIN'
};
创建 Action 后,下一步是将操作发送到 Redux store,以便它可以更新其状态。 在 Redux 中,您可以定义 Action 创建者来完成此操作。 Action 创建者只是一个返回 Action 的 JavaScript 函数。 换句话说,Action 创建者创建表示 Action 事件的对象。 定义一个名为
actionCreator()
的函数,该函数在调用时返回action
对象。
const action = {
type: 'LOGIN'
}
// Define an action creator here:
function actionCreator() {
return action;
}
dispatch
方法是用于将 Action 调度到 Redux store 的方法。 调用store.dispatch()
并传递从 Action 创建者返回的值会将操作发送回 store。 回想一下,Action 创建者返回一个对象,该对象具有指定已发生的 Action 的类型属性。 然后,该方法将操作对象调度到 Redux 存储区。 根据前面挑战的示例,以下行是等效的,并且都调度类型LOGIN
的 Action:
store.dispatch(actionCreator());
store.dispatch({ type: 'LOGIN' });
代码编辑器中的 Redux store 具有初始化状态, 该状态是包含当前设置为
false
的login
属性的对象。 还有一个名为loginAction()
的 action 创建器,它返回类型LOGIN
的 action。 通过调用dispatch
方法将LOGIN
action 调度到 Redux store, 并传入loginAction()
创建的 action。
const store = Redux.createStore(
(state = {login: false}) => state
);
const loginAction = () => {
return {
type: 'LOGIN'
}
};
// Dispatch the action here:
store.dispatch(loginAction());
创建 action 并 dispatch 后, Redux store 需要知道如何响应 action。 这是
reducer
函数的工作, Redux 中的reducer
负责为响应 action 而发生的状态修改。reducer
将state
和action
作为参数,并且始终返回新的state
。 Redux 中的另一个关键原则是state
是只读的。 换句话说,reducer
函数必须始终返回state
的新副本,并且从不直接修改状态。 Redux 不强制要求状态不可变性,但是,你负责在reducer
函数的代码中强制执行它。
const defaultState = {
login: false
};
const reducer = (state = defaultState, action) => {
// Change code below this line
if (action.type === "LOGIN") {
return {
login: true
};
} else {
return state;
}
// Change code above this line
};
const store = Redux.createStore(reducer);
const loginAction = () => {
return {
type: 'LOGIN'
}
};
const defaultState = {
authenticated: false
};
const authReducer = (state = defaultState, action) => {
// Change code below this line
switch (action.type) {
case "LOGIN":
return {
authenticated: true
};
case "LOGOUT":
return {
authenticated: false
};
default:
return defaultState;
}
// Change code above this line
};
const store = Redux.createStore(authReducer);
const loginUser = () => {
return {
type: 'LOGIN'
}
};
const logoutUser = () => {
return {
type: 'LOGOUT'
}
};
使用 Redux 时的常见做法是将 Action 类型分配为只读常量, 然后在使用这些常量的位置引用这些常量。 可以重构正在使用的代码,以将 Action 类型编写为
const
声明。 将LOGIN
和LOGOUT
声明为const
值, 并分别将它们分配给字符串'LOGIN'
和'LOGOUT'
。 然后,编辑authReducer()
和 Action 创建者以引用这些常量而不是字符串值。 注意:通常约定以 全大写 形式写入常量,这也是 Redux 中的标准做法。
const LOGIN = "LOGIN";
const LOGOUT = "LOGOUT";
const defaultState = {
authenticated: false
};
const authReducer = (state = defaultState, action) => {
switch (action.type) {
case LOGIN:
return {
authenticated: true
}
case LOGOUT:
return {
authenticated: false
}
default:
return state;
}
};
const store = Redux.createStore(authReducer);
const loginUser = () => {
return {
type: LOGIN
}
};
const logoutUser = () => {
return {
type: LOGOUT
}
};
你有权访问 Redux
store
对象的另一个方法是store.subscribe()
。 这允许你将侦听器函数订阅到 store, 每当针对 store dispatch action 时都会调用这些函数。 此方法的一个简单用途是向您的 store 订阅一个函数, 该函数仅在每次收到 action 并更新 store 时记录一条消息。 编写一个回调函数,每次 store 收到 action 时递增全局变量count
, 并将此函数传递给store.subscribe()
方法。 你将看到连续调用store.dispatch()
三次, 每次都直接传入一个 action 对象。 观察 action dispatch 之间的控制台输出,以查看更新的发生情况。
const ADD = 'ADD';
const reducer = (state = 0, action) => {
switch(action.type) {
case ADD:
return state + 1;
default:
return state;
}
};
const store = Redux.createStore(reducer);
// Global count variable:
let count = 0;
// Change code below this line
store.subscribe(() => count++);
// Change code above this line
store.dispatch({type: ADD});
console.log(count);
store.dispatch({type: ADD});
console.log(count);
store.dispatch({type: ADD});
console.log(count);
reducer
当应用的状态开始变得更加复杂时,可能很容易将状态划分为多个部分。 相反,请记住 Redux 的第一个原则: 所有应用状态都保存在 store 中的单个状态对象中。 因此,Redux 提供了
reducer
组合作为复杂状态模型的解决方案。 定义多个reducer
来处理应用程序状态的不同部分, 然后将这些reducer
组合成一个根reducer
(root reducer
)。 然后将根reducer
传递到 ReduxcreateStore()
方法中。 为了让我们将多个reducer
组合在一起,Redux 提供了combineReducers()
方法。 此函数接受对象作为参数,您可以在其中定义将键关联到特定reducer
函数的属性。 例如,在具有用户身份验证的笔记应用中, 一个reducer
可以处理身份验证, 而另一个reducer
可以处理用户正在提交的文本和笔记。
const rootReducer = Redux.combineReducers({
auth: authenticationReducer,
notes: notesReducer
});
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
const counterReducer = (state = 0, action) => {
switch(action.type) {
case INCREMENT:
return state + 1;
case DECREMENT:
return state - 1;
default:
return state;
}
};
const LOGIN = 'LOGIN';
const LOGOUT = 'LOGOUT';
const authReducer = (state = {authenticated: false}, action) => {
switch(action.type) {
case LOGIN:
return {
authenticated: true
}
case LOGOUT:
return {
authenticated: false
}
default:
return state;
}
};
const rootReducer = Redux.combineReducers({
count: counterReducer,
auth: authReducer
}); // Define the root reducer here
const store = Redux.createStore(rootReducer);
const ADD_NOTE = 'ADD_NOTE';
const notesReducer = (state = 'Initial State', action) => {
switch(action.type) {
// Change code below this line
case ADD_NOTE:
return action.text;
// Change code above this line
default:
return state;
}
};
const addNoteText = (note) => {
// Change code below this line
return {
type: ADD_NOTE,
text: note
};
// Change code above this line
};
const store = Redux.createStore(notesReducer);
console.log(store.getState());
store.dispatch(addNoteText('Hello!'));
console.log(store.getState());
const REQUESTING_DATA = 'REQUESTING_DATA'
const RECEIVED_DATA = 'RECEIVED_DATA'
const requestingData = () => { return {type: REQUESTING_DATA} }
const receivedData = (data) => { return {type: RECEIVED_DATA, users: data.users} }
const handleAsync = () => {
return function(dispatch) {
// Dispatch request action here
dispatch(requestingData());
setTimeout(function() {
let data = {
users: ['Jeff', 'William', 'Alice']
}
// Dispatch received data action here
dispatch(receivedData(data));
}, 2500);
}
};
const defaultState = {
fetching: false,
users: []
};
const asyncDataReducer = (state = defaultState, action) => {
switch(action.type) {
case REQUESTING_DATA:
return {
fetching: true,
users: []
}
case RECEIVED_DATA:
return {
fetching: false,
users: action.users
}
default:
return state;
}
};
const store = Redux.createStore(
asyncDataReducer,
Redux.applyMiddleware(ReduxThunk.default)
);
// Define a constant for increment action types
const INCREMENT = "INCREMENT";
// Define a constant for decrement action types
const DECREMENT = "DECREMENT";
// Define the counter reducer which will increment or decrement the state based on the action it receives
const counterReducer = (state = 0, action) => {
switch(action.type) {
case INCREMENT:
return state + 1;
case DECREMENT:
return state - 1;
default:
return state;
}
};
// Define an action creator for incrementing
const incAction = () => {
return {
type: INCREMENT
};
};
// Define an action creator for decrementing
const decAction = () => {
return {
type: DECREMENT
};
};
// Define the Redux store here, passing in your reducers
const store = Redux.createStore(counterReducer);
不可变状态意味着你从不直接修改状态,而是返回一个新的状态副本。 Redux 不会主动在其 store 或 reducer 中强制执行状态不变性, 该责任落在程序员身上。 代码编辑器中有一个
store
和reducer
用于管理待办事项。 完成在reducer
中写入案例ADD_TO_DO
以将新的待办事项附加到状态。 有几种方法可以使用标准 JavaScript 或 ES6 来完成此操作。 看看你是否可以找到一种方法来返回一个新数组, 其中的项目action.todo
附加到末尾。 由于 Redux 中的状态不变性, 此挑战的目标是在 reducer 函数中返回一个新的状态副本。
const ADD_TO_DO = 'ADD_TO_DO';
// A list of strings representing tasks to do:
const todos = [
'Go to the store',
'Clean the house',
'Cook dinner',
'Learn to code',
];
const immutableReducer = (state = todos, action) => {
switch(action.type) {
case ADD_TO_DO:
// Don't mutate state here or the tests will fail
// 拼接, 加在数组最后
// return state.concat(action.todo);
// 或 ES6 展开运算符
return [...state, action.todo]
default:
return state;
}
};
const addToDo = (todo) => {
return {
type: ADD_TO_DO,
todo
}
}
const store = Redux.createStore(immutableReducer);
...
展开运算符有多种应用,其中之一非常适合 从现有数组生成新数组
let newArray = [...myArray];
newArray
现在是克隆myArray
。 两个数组仍然单独存在于内存中。 如果你执行类似newArray.push(5)
,myArray
则不会改变。
const immutableReducer = (state = ['Do not mutate state!'], action) => {
switch(action.type) {
case 'ADD_TO_DO':
// Don't mutate state here or the tests will fail
let newArray = [...state, action.todo];
return newArray;
default:
return state;
}
};
const addToDo = (todo) => {
return {
type: 'ADD_TO_DO',
todo
}
}
const store = Redux.createStore(immutableReducer);
const immutableReducer = (state = [0,1,2,3,4,5], action) => {
switch(action.type) {
case 'REMOVE_ITEM':
// Don't mutate state here or the tests will fail
return [
...state.slice(0, action.index),
...state.slice(action.index + 1, state.length)
];
// 或
// return state.slice(0, action.index).concat(state.slice(action.index + 1, state.length));
// 或
// return state.filter((_, index) => index !== action.index);
default:
return state;
}
};
const removeItem = (index) => {
return {
type: 'REMOVE_ITEM',
index
}
}
const store = Redux.createStore(immutableReducer);
Object.assign
复制一个对象
Object.assign()
获取目标对象和源对象并将属性从源对象映射到目标对象。 任何匹配的属性都会被源对象中的属性覆盖。 此行为通常用于通过传递一个空对象作为第一个参数, 然后传递要复制的对象来制作对象的浅表副本。 这是一个例子:
const newObject = Object.assign({}, obj1, obj2);
const defaultState = {
user: 'CamperBot',
status: 'offline',
friends: '732,982',
community: 'freeCodeCamp'
};
const immutableReducer = (state = defaultState, action) => {
switch(action.type) {
case 'ONLINE':
// Don't mutate state here or the tests will fail
return Object.assign({}, state, { status: "online" });
default:
return state;
}
};
const wakeUp = () => {
return {
type: 'ONLINE'
}
};
const store = Redux.createStore(immutableReducer);
class DisplayMessages extends React.Component {
// Change code below this line
constructor(props) {
super(props);
this.state = {
input: "",
messages: []
};
}
// Change code above this line
render() {
return <div />
}
};
class DisplayMessages extends React.Component {
constructor(props) {
super(props);
this.state = {
input: '',
messages: []
};
this.handleChange = this.handleChange.bind(this);
this.submitMessage = this.submitMessage.bind(this);
}
// Add handleChange() and submitMessage() methods here
handleChange(event) {
// 无需更新 messages, 会自动合并更新, 不会导致 messages 丢失
this.setState({
input: event.target.value
});
}
submitMessage() {
this.setState(state => ({
input: "",
messages: [...state.messages, state.input]
}));
}
render() {
return (
<div>
<h2>Type in a new Message:</h2>
{ /* Render an input, button, and ul below this line */ }
<input onChange={this.handleChange} value={this.state.input} />
<button onClick={this.submitMessage}>Add message</button>
<ul>
{
this.state.messages.map((m, i) =>
(
<li key={i}>{m}</li>
)
)
}
</ul>
{ /* Change code above this line */ }
</div>
);
}
};
// Define ADD, addMessage(), messageReducer(), and store here:
const ADD = "ADD";
const addMessage = message => {
return {
type: ADD,
message
};
};
// Use ES6 default paramter to give the 'previousState' parameter an initial value.
const messageReducer = (previousState = [], action) => {
// Use switch statement to lay out the reducer logic in response to different action type
switch (action.type) {
case ADD:
// Use ES6 spread operator to return a new array where the new message is added to previousState
return [...previousState, action.message];
default:
// A default case to fall back on in case if the update to Redux store is not for this specific state.
return previousState;
}
};
const store = Redux.createStore(messageReducer);
下一步是提供对 Redux store 的 React 访问以及调度(dispatch)更新所需的 action。 React Redux 提供了它的
react-redux
包来帮助完成这些任务。 React Redux 提供了一个具有两个关键特性的小型 API:Provider
和connect
。Provider
需要两个 props,即 Redux store 和应用的子组件。 为 App 组件定义Provider
可能如下所示:
<Provider store={store}>
<App/>
</Provider>
// Redux:
const ADD = 'ADD';
const addMessage = (message) => {
return {
type: ADD,
message
}
};
const messageReducer = (state = [], action) => {
switch (action.type) {
case ADD:
return [
...state,
action.message
];
default:
return state;
}
};
const store = Redux.createStore(messageReducer);
// React:
class DisplayMessages extends React.Component {
constructor(props) {
super(props);
this.state = {
input: '',
messages: []
}
this.handleChange = this.handleChange.bind(this);
this.submitMessage = this.submitMessage.bind(this);
}
handleChange(event) {
this.setState({
input: event.target.value
});
}
submitMessage() {
this.setState((state) => {
const currentMessage = state.input;
return {
input: '',
messages: state.messages.concat(currentMessage)
};
});
}
render() {
return (
<div>
<h2>Type in a new Message:</h2>
<input
value={this.state.input}
onChange={this.handleChange}/><br/>
<button onClick={this.submitMessage}>Submit</button>
<ul>
{this.state.messages.map( (message, idx) => {
return (
<li key={idx}>{message}</li>
)
})
}
</ul>
</div>
);
}
};
const Provider = ReactRedux.Provider;
class AppWrapper extends React.Component {
// Render the Provider below this line
render() {
return (
<Provider store={store}>
<DisplayMessages />
</Provider>
);
}
// Change code above this line
};
Provider
组件允许您为 React 组件提供state
和dispatch
, 但你必须准确指定所需的 state 和 action。 这样,您可以确保每个组件只能访问它所需的 state。 你可以通过创建两个函数来实现此目的:mapStateToProps()
和mapDispatchToProps()
。
const state = [];
// Change code below this line
const mapStateToProps = (state) => {
return {
messages: state
};
};
mapDispatchToProps()
函数用于为你的 React 组件提供特定的操作(action)创建者, 以便它们可以针对 Redux store 调度(dispatch) 操作(action)。 一个示例
{
submitLoginUser: function(username) {
dispatch(loginUser(username));
}
}
const addMessage = (message) => {
return {
type: 'ADD',
message: message
}
};
// Change code below this line
const mapDispatchToProps = (dispatch) => {
return {
submitNewMessage: (message) => {
dispatch(addMessage(message));
}
};
};
connect(mapStateToProps, mapDispatchToProps)(MyComponent)
const addMessage = (message) => {
return {
type: 'ADD',
message: message
}
};
const mapStateToProps = (state) => {
return {
messages: state
}
};
const mapDispatchToProps = (dispatch) => {
return {
submitNewMessage: (message) => {
dispatch(addMessage(message));
}
}
};
class Presentational extends React.Component {
constructor(props) {
super(props);
}
render() {
return <h3>This is a Presentational Component</h3>
}
};
const connect = ReactRedux.connect;
// Change code below this line
const ConnectedComponent = connect(mapStateToProps, mapDispatchToProps)(Presentational);
// Redux:
const ADD = 'ADD';
const addMessage = (message) => {
return {
type: ADD,
message: message
}
};
const messageReducer = (state = [], action) => {
switch (action.type) {
case ADD:
return [
...state,
action.message
];
default:
return state;
}
};
const store = Redux.createStore(messageReducer);
// React:
class Presentational extends React.Component {
constructor(props) {
super(props);
this.state = {
input: '',
messages: []
}
this.handleChange = this.handleChange.bind(this);
this.submitMessage = this.submitMessage.bind(this);
}
handleChange(event) {
this.setState({
input: event.target.value
});
}
submitMessage() {
this.setState((state) => {
const currentMessage = state.input;
return {
input: '',
messages: state.messages.concat(currentMessage)
};
});
}
render() {
return (
<div>
<h2>Type in a new Message:</h2>
<input
value={this.state.input}
onChange={this.handleChange}/><br/>
<button onClick={this.submitMessage}>Submit</button>
<ul>
{this.state.messages.map( (message, idx) => {
return (
<li key={idx}>{message}</li>
)
})
}
</ul>
</div>
);
}
};
// React-Redux:
const mapStateToProps = (state) => {
return { messages: state }
};
const mapDispatchToProps = (dispatch) => {
return {
submitNewMessage: (newMessage) => {
dispatch(addMessage(newMessage))
}
}
};
const Provider = ReactRedux.Provider;
const connect = ReactRedux.connect;
// Define the Container component here:
const Container = connect(mapStateToProps, mapDispatchToProps)(Presentational);
class AppWrapper extends React.Component {
constructor(props) {
super(props);
}
render() {
// Complete the return statement:
return (
<Provider store={store}>
<Container />
</Provider>
);
}
};
现在 Redux 已连接, 你需要将状态管理从
Presentational
组件中提取到 Redux 中。 目前,你已连接 Redux, 但你正在Presentational
组件中本地处理状态。 在Presentational
组件中, 首先删除本地state
中的messages
属性。 这些消息将由 Redux 管理。 接下来,修改submitMessage()
方法, 使其从this.props
调度submitNewMessage()
, 并将来自本地state
的当前消息输入作为参数传入。 由于你从本地状态中删除了messages
, 因此也在此处从对this.setState()
的调用中删除了messages
属性。 最后,修改render()
方法, 使其映射从props
而不是state
接收的消息。 进行这些更改后,应用将继续正常运行,但 Redux 管理状态除外。 此示例还说明了组件如何具有本地state
: 你的组件仍然在其自己的state
中本地跟踪用户输入。 你可以看到 Redux 如何在 React 之上提供一个有用的状态管理框架。 一开始,你只使用 React 的本地状态就获得了相同的结果, 这通常可以通过简单的应用程序来实现。 但是,随着你的应用程序变得更大、更复杂, 你的状态管理也会变得更复杂,这就是 Redux 解决的问题。 本地 state 管理input
, 其它交给 Redux (message
存到Redux store
), 连接 React 与 Redux: 1. 将 Redux state 映射到 React 的 props 中Redux state 存储数据 React 从 props 中访问 Redux 存储的状态数据将 Redux dispatch 映射到 React 的 props 中Redux dispatch 更新状态数据 React 从 props 中取出来更新 Redux 管理的状态数据
// Redux:
const ADD = 'ADD';
const addMessage = (message) => {
return {
type: ADD,
message: message
}
};
const messageReducer = (state = [], action) => {
switch (action.type) {
case ADD:
return [
...state,
action.message
];
default:
return state;
}
};
const store = Redux.createStore(messageReducer);
// React:
const Provider = ReactRedux.Provider;
const connect = ReactRedux.connect;
// Change code below this line
class Presentational extends React.Component {
constructor(props) {
super(props);
// Remove property 'messages' from Presentational's local state
this.state = {
input: '',
// messages: []
}
this.handleChange = this.handleChange.bind(this);
this.submitMessage = this.submitMessage.bind(this);
}
handleChange(event) {
this.setState({
input: event.target.value
});
}
submitMessage() {
// Call 'submitNewMessage', which has been mapped to Presentational's props, with a new message;
// meanwhile, remove the 'messages' property from the object returned by this.setState().
this.props.submitNewMessage(this.state.input);
this.setState({
input: ''
});
}
render() {
return (
<div>
<h2>Type in a new Message:</h2>
<input
value={this.state.input}
onChange={this.handleChange}/><br/>
<button onClick={this.submitMessage}>Submit</button>
<ul>
{/* The messages state is mapped to Presentational's props; therefore, when rendering,
you should access the messages state through props, instead of Presentational's
local state. */}
{this.props.messages.map( (message, idx) => {
return (
<li key={idx}>{message}</li>
)
})
}
</ul>
</div>
);
}
};
// Change code above this line
const mapStateToProps = (state) => {
return {messages: state}
};
const mapDispatchToProps = (dispatch) => {
return {
submitNewMessage: (message) => {
dispatch(addMessage(message))
}
}
};
const Container = connect(mapStateToProps, mapDispatchToProps)(Presentational);
class AppWrapper extends React.Component {
render() {
return (
<Provider store={store}>
<Container/>
</Provider>
);
}
};
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider, connect } from 'react-redux'
import { createStore, combineReducers, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from './redux/reducers'
import App from './components/App'
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
);
// Only change code below this line
console.log("Now I know React and Redux!");
参考:
参考:
参考:
// 注释内容
在 Sass 中,这种注释方式在 编译后不会保留下来。
/* 注释内容 */
在 Sass 中,这种注释方式在 编译后会保留下来。 因为这种注释方式跟 CSS 注释方式是相同的,所以编译后会保留下来。
/*! 注释内容 */
我们都知道压缩工具会删除所有的注释, 有些时候为了保留一些版权声明的注释说明,可以采用以下方式:
/*! 注释内容 */
也就是说在注释内容前面加上一个
!
,这种压缩工具就不会删除这条注释信息了。 不过这种注释方式用得很少,一般在 CSS 文件顶部为了声明版权信息才会使用。 Sass
/*! Copyright ©2023-present yiyun, All Rights Reserved */
$height: 20px;
body {
height: $height;
line-height: $height;
}
编译出来的 CSS 代码如下: CSS
/*! Copyright ©2023-present yiyun, All Rights Reserved */
body {
height: 20px;
line-height: 20px;
}
感谢帮助!