Marco Nie - web https://blog.niekun.net/category/web/ zh-CN Fri, 12 Aug 2022 14:50:00 +0800 Fri, 12 Aug 2022 14:50:00 +0800 Miniflux 搭建自己的 RSS 服务系统 https://blog.niekun.net/archives/2749.html https://blog.niekun.net/archives/2749.html Fri, 12 Aug 2022 14:50:00 +0800 admin 一直在使用 feedly 作为 rss 订阅器阅读文章,基本可以满足我的需求,但是部分站点只能预览摘要,想要阅读全文还打开文章链接。最近发现一个开源免费的 rss 系统,他的特点就是轻量无多于内容,致力于阅读体验。同时我发现他的一些独有功能,可以在文章只显示摘要时,有一个下载全文的选项,这样就实现了大部分文章在 rss 阅读器中就可以阅读全文的需求了。

Miniflux 需要自己部署在服务器上,它提供了多种安装方法,最简单的就是 docker 方式,避免手动配置环境及数据库等步骤。

我的系统环境:Ubuntu server 20.04

Miniflux 官网:https://miniflux.app/
GitHub 主页:https://github.com/miniflux/v2

安装

这里介绍通过 docker compose 安装的方法,关于 docker 环境的部署参考我的教程:https://blog.niekun.net/archives/2742.html#title-1

新建 miniflux 文件夹用来放置相关配置:

mkdir miniflux
cd miniflux

然后建立 docker-compose.yml 配置文件,内容如下:

version: '3.4'
services:
  miniflux:
    image: ${MINIFLUX_IMAGE:-miniflux/miniflux:latest}
    container_name: miniflux
    restart: always
    ports:
      - "18080:8080"
    depends_on:
      - db
    environment:
      - DATABASE_URL=postgres://miniflux:secret@db/miniflux?sslmode=disable
      - BASE_URL=https://miniflux.your.domain
      - RUN_MIGRATIONS=1
      - CREATE_ADMIN=1
      - ADMIN_USERNAME=admin
      - ADMIN_PASSWORD=test123
      - DEBUG=1
    # Optional health check:
    # healthcheck:
    #  test: ["CMD", "/usr/bin/miniflux", "-healthcheck", "auto"]
  db:
    image: postgres:latest
    container_name: postgres
    restart: always
    environment:
      - POSTGRES_USER=miniflux
      - POSTGRES_PASSWORD=secret
    volumes:
      - miniflux-db:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "miniflux"]
      interval: 10s
      start_period: 30s
volumes:
  miniflux-db:

其中需要自行根据实际情况修改一些内容:

  • port 端口的主机映射,这里我使用了 18080 映射容器内的 8080 端口
  • BASE_URL 设置需要访问 miniflux 服务的域名地址,后面需要配置反向代理
  • ADMIN_USERNAME 设置管理员用户名
  • ADMIN_PASSWORD 设置管理员用户密码

敏感的环境变量值可以单独放在同配置文件路径下的 .env 文件中,上面的 docker 安装教程中有介绍。

注意 DATABASE_URL 地址中的 postgres 用户名密码对应于 POSTGRES_USER 和 POSTGRES_PASSWORD 的值,需要保持一致。

然后就可以启动容器:

docker-compose up -d

反代配置

首先如果需要解析到二级域名下,先要在 ns 服务端添加二级域名的 A 记录,然后才能正常解析 url。

使用域名访问 miniflux 服务,需要通过主机使用的反代软件配置解析,我服务器使用的是 nginx,下面介绍配置方法。

给 nginx 配置添加如下内容:

server {
    listen        443 ssl http2;
    listen        [::]:443 ssl http2;
    server_name   miniflux.your.domain;
    include       my-server/ssl;

    location / {
        proxy_pass          http://127.0.0.1:18080;
        proxy_redirect      off;

        proxy_set_header    Host              $host;
        proxy_set_header    X-Forwarded-Host  $host;
        proxy_set_header    X-Real-IP         $remote_addr;
        proxy_set_header    X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header    X-Forwarded-Proto $scheme;
        proxy_set_header    Upgrade           $http_upgrade;
        proxy_set_header    Connection        "upgrade";
        proxy_http_version  1.1;
    }
}

以上配置需要根据实际情况修改监听端口及 domain,由于之前配置的 miniflux 的映射端口是 18080,所以反代到本地的对应端口即可。

以上 miniflux 和 nginx 反代配置完成后应该就可以访问 https://miniflux.your.domain 了。用户名密码就是 docker 配置文件中定义的管理员账户及密码:
2022-08-12T06:47:50.png

使用

如果某一篇文章不显示全文,只需要点击文章顶部的 download 按钮即可加载全文:
2022-08-12T06:47:12.png

miniflux 提供了丰富的 api 接口可供二次开发使用,通过简单的请求就可以获取到文章的各种信息,返回数据为 json 格式。

官方 api 文档参考:https://miniflux.app/docs/api.html

]]>
0 https://blog.niekun.net/archives/2749.html#comments https://blog.niekun.net/feed/category/web/archives/2749.html
获取 JD cookie 方法 https://blog.niekun.net/archives/2388.html https://blog.niekun.net/archives/2388.html Sun, 09 Jan 2022 18:53:00 +0800 admin 在运行自动化脚本的时候需要获取到对应网站的登录 cookie,这里介绍通过 chrome 浏览器获取到需要的信息。

这里提供的方法仅供用于前端开发测试。

首先登陆移动端网站:https://m.jd.com

右键点击 inspect 进入开发者界面,然后点击网页右上角的登录键根据提示需要输入账户信息登录。

登录成功后在开发栏切换到 network 选项,并且在 filter 中输入 log.gif 过滤出需要的信息:
1.jpg

选中第一个 log 文件,点击 header 栏,在其中找到 cookie 项:
2.jpg

复制其中的内容到剪贴板。

找到开发页面下方的 console 栏,我们通过简单的 js 脚本提取出上面 cookie 的有效内容:

var c = '这里粘贴复制好的 cookie';
var value = c.match(/pt_pin=.+?;/) + c.match(/pt_key=.+?;/);
copy(value);

执行完成后此时我们的剪贴板里就已经复制好了 cookie 信息。


手机端可以通过解包 app 来提取 cookie,具体可以参考我的 js 脚本:https://github.com/nie11kun/config-rules/blob/master/Shadowrocket/get_jdcookie.js

]]>
0 https://blog.niekun.net/archives/2388.html#comments https://blog.niekun.net/feed/category/web/archives/2388.html
React 入门教程之九 -- composition 模块化 和 inheritance 继承 https://blog.niekun.net/archives/2329.html https://blog.niekun.net/archives/2329.html Sun, 29 Aug 2021 13:35:29 +0800 admin react 有一套完善了 composition 构造模型,推荐使用 composition 代替 inheritance 来在 components 之间复用代码。下面介绍在开发的具体场景中常常需要用到 inheritance 的地方如何用 composition 解决。

containment 包含

一些 components 并不直接知道他们的 children 具体是什么。在 Sidebar 或 Dialog 中可以体现,它们只是一个 box 容器,他的内容可能是变化的。

这一类的 components 推荐直接使用 children props 来直接表示 parent 传递给他们的 elements:

const FancyBorder = props => {
    return (
        <div className={`FancyBorder FancyBorder-${props.color}`}>
            {props.children}
        </div>
    );
}

props.children 表示所有在调用此 component 时放在其元素中的内容。

然后我们创建一个 component 来调用上面的 FancyBorder:

const WelcomeDialog = () => {
    return (
        <FancyBorder color='red'>
            <h1 className='Dialog-title'>Welcome</h1>
            <p className='Dialog-message'>
                thank you for check this page.
            </p>
        </FancyBorder>
    )
}

任何在 FancyBorder 标签中的内容都会作为 children prop 传入 FancyBorder 中,然后通过 props.children 进行渲染。

但通常情况下我们的 compenent 可能会有多个 “入口”,此时我们就需要定义自己的 convention 声明来替代 children:

const React = require('react')
const ReactDOM = require('react-dom')

const SplitPane = (props) => {
    return (
        <div className='SplitPane'>
            <div className='Splitpane-left'>
                {props.left}
            </div>
            <div className='Splitpane-right'>
                {props.right}
            </div>
        </div>
    )
}

const Contacts = () => {
    return (
        <div>
            <h1>marco</h1>
        </div>
    )
}

const Chat = () => {
    return (
        <div>
            <p>this is a test</p>
        </div>
    )
}

const App = () => {
    return (
        <SplitPane
            left={
                <Contacts />
            }
            right={
                <Chat />
            }
        />
    )
}

ReactDOM.render(
    <App />,
    document.getElementById('root')
);

可以看到,我们可以将 component 像其他属性一样传递,这里 Contacts 和 Chat 就作为 left 和 right 的数据传递给 SplitPane 使用。

specialization 特殊化

一些场景下,我们会将某个 component 看作另一个 component 的特殊情况,例如上面示例中的 WelcomeDialog 可以看做是 Dialog 的特殊情况。在 react 中我们通常通过给一个 generic 泛用的 component 配置 props 的方式构成另一个 special component:

const React = require('react')
const ReactDOM = require('react-dom')

const FancyBorder = props => {
    return (
        <div className={`FancyBorder FancyBorder-${props.color}`}>
            {props.children}
        </div>
    );
}

const Dialog = props => {
    return (
        <FancyBorder color='red'>
            <h1 className='Dialog-title'>
                {props.title}
            </h1>
            <p className='Dialog-message'>
                {props.message}
            </p>
        </FancyBorder>
    )
}
const WelcomeDialog = () => {
    return (
        <Dialog 
            title='Welcome'
            message='welcome to this party'
        />
    )
}

ReactDOM.render(
    <WelcomeDialog />,
    document.getElementById('root')
);

我们也可以通过 class 的方式定义 component:

const React = require('react')
const ReactDOM = require('react-dom')

const FancyBorder = props => {
    return (
        <div className={`FancyBorder FancyBorder-${props.color}`}>
            {props.children}
        </div>
    );
}

const Dialog = props => {
    return (
        <FancyBorder color='red'>
            <h1 className='Dialog-title'>
                {props.title}
            </h1>
            <p className='Dialog-message'>
                {props.message}
            </p>
            {props.children}
        </FancyBorder>
    )
}

class SignUpDialog extends React.Component {
    constructor(props) {
        super(props);
        this.handleChangle = this.handleChangle.bind(this);
        this.handleSignup = this.handleSignup.bind(this);
        this.state = {login: ''};
    }

    handleChangle(e) {
        this.setState({login: e.target.value});
    }

    handleSignup() {
        alert(`welcome guys, ${this.state.login}`)
    }

    render () {
        return (
            <Dialog
                title='Sport game'
                message='welcome to this game'
            >
                <input value={this.state.login} onChange={this.handleChangle} />
                <button onClick={this.handleSignup}>Sign me up</button>
            </Dialog>
        )
    }
}

ReactDOM.render(
    <SignUpDialog />,
    document.getElementById('root')
);

这里我们使用了自定义 props 和 children props 来构建了 SignUpDialog component,根据实际场景灵活使用。

props 和 composition 提供了灵活性来自定义一个 component 的样式/行为,且更加安全和精确。需要注意的是 component 可能会接收到 arbitrary 抽象 props,包括原始二进制数据/elements/functions 等。

]]>
0 https://blog.niekun.net/archives/2329.html#comments https://blog.niekun.net/feed/category/web/archives/2329.html
React 入门教程之八 -- Lifting State Up 提升 state 层级 https://blog.niekun.net/archives/2324.html https://blog.niekun.net/archives/2324.html Sat, 28 Aug 2021 23:38:00 +0800 admin 大多数情况下,不同的 components 之间需要对同一个变化着的 data 进行响应。推荐将这些 shared state 共享的数据提升到它们最近的 parent component 中,下面详细介绍如何实现这一 function。

下面创建一个 temperature calculator 温度计算器来判断在一个给定的温度下,水是否会沸腾。

首先我们创建一个 BoilingVerdict component作为沸腾裁决器,它接受 celsius 摄氏温度作为一个 prop,然后输出是否足够使水沸腾:

const BoilingVerdict = (props) => {
    if (props.celsius >= 100) {
        return <p>The Water would boil.</p>
    }
    return <p>The water would not boil.</p>
} 

然后我们创建一个 Calculator compenent,它会渲染一个 input 元素用来输入温度数据,且将数据存储在 this.state.temprature 中:

const React = require('react')
const ReactDOM = require('react-dom')

const BoilingVerdict = (props) => {
    if (props.celsius >= 100) {
        return <p>The Water would boil.</p>
    }
    return <p>The water would not boil.</p>
} 

class Calculator extends React.Component {
    constructor(props) {
        super(props);
        this.state = {temperature: ''};

        this.handleChange = this.handleChange.bind(this);
    }

    handleChange(e) {
        this.setState({temperature: e.target.value});
    }

    render() {
        const temperature = this.state.temperature;
        return (
            <fieldset>
                <legend>Enter temperature in celsius</legend>
                <input
                    value={temperature}
                    onChange={this.handleChange}
                />
                <BoilingVerdict celsius={parseFloat(temperature)} />
            </fieldset>
        );
    }
}

ReactDOM.render(
    <Calculator />,
    document.getElementById('root')
);

上面的示例中使用了一个 fieldset 来定义一个块,legend 定义了这个块的抬头信息,效果如下:

2021-08-28T13:36:45.png

上一章节中我们详细介绍了如何构建一个 controlled component 可以参考。

现在我们有一个新的需求,在原有 celsius 摄氏输入的同时增加一个 Fahrenheit 华氏输入栏,同时让它们两者保持数据同步。

首先我们从 Calculator 中拆解出一个 TempratureInput component,将会给其增加一个 scale props,它可以是 "c" 或 "f" 来区分摄氏和华氏:

const scaleNames = {
    c: 'Celsius',
    f: 'Fahrenheit'
}

class TemperatureInput extends React.Component {
    constructor(props) {
        super(props);
        this.state = {temperature: ''};

        this.handleChange = this.handleChange.bind(this);
    }

    handleChange(e) {
        this.setState({temperature: e.target.value});
    }

    render() {
        const temperature = this.state.temperature;
        const scale = this.props.scale;
        return (
            <fieldset>
                <legend>Enter temperature in {scaleNames[scale]}:</legend>
                <input
                    value={temperature}
                    onChange={this.handleChange}
                />
            </fieldset>
        );
    } 
}

class Calculator extends React.Component {
    render() {
        return (
            <div>
                <TemperatureInput scale='c' />
                <TemperatureInput scale='f' />
            </div>
        );
    }
}

修改后,我们有了两个 fieldset,分别用来输入摄氏和华氏温度值,我们建立了一个 scaleNames object 用来简化 scale 属性的定义,只需要通过调用 object props 的方式即可引用我们想要的文本全称,这里使用了 ES6 的 Computed Property Names 预定义属性名,通过 scaleNames[scale] 动态调用其属性。此时两个 input 是相互隔离的,它们的数据不能相互访问。

下面我们实现两个 component 之间的数据互通。

首先我们建立两个 function 用来实现 celsius 和 Fahrenheit 之前的互相转换:

const toCelsius = fahrenheit => (fahrenheit - 32) * 5 / 9;
const toFahrenheit = celsius => (celsius * 9 / 5) + 32;

这里我使用了 ES6 的简化写法,省略了 return 等符号。

下面我们编写另一个 function 接收两个数据,一个是 string 字符串和一个 function,用来将输入的 temperature 数据转换并返回转换好的字符串,当输入的 string 不是无法转换为数字时会返回一个空字符串,可以转换时将精度设置为 3 位小数:

const tryConvert = (temperature, convert) => {
    const input = parseFloat(temperature);
    if (Number.isNaN(input)) {
        return '';
    }
    const output = convert(input);
    const rounded = Math.round(output * 1000) / 1000;
    return rounded.toString();
}

使用上面的 function 如果执行 tryConvert('abc', toCelsius) 会返回空字符串,如果执行 tryConvert('10.22', toFahrenheit) 会返回 "50.396"

下面我们将 TempratureInput 中的 state 提升到 parent component。

当前的代码中,每个 TempratureInput component 各自控制它们的 state 且相互隔离,但是我们希望这两个 inputs 能够共享数据且同步更新,例如当我修改了 celsius input 后 Fahrenheit input 会自动更改为对应 celsius input 的结果。

在 react 中,共享 state 通过将其移动到这些 components 最近层级中的 parent component 中,叫做 lifting state up,下面我们将 TempratureInput 中的本地 state 移动到 Calculator 中。如果 Calculator 含有 shared state,它就成为了其下级 components 的 source of truth 可信来源,它可以管理他的下级 components 保持数据一致性,因此两个 TempratureInput 的 props 都来自于同一个 Calculator,所以他们的 inputs 将会保持一致同步.

下面我们逐步实现这个过程,首先替换 TemperatureInput component 中的 this.state.temperaturethis.props.temperature,稍后我们将在 Calculator 中定义它:

  render() {
    // Before: const temperature = this.state.temperature;
    const temperature = this.props.temperature;
    // ...

我们知道 props 是只读的,在之前我们的 temperature 存储在本地的 state 中,然后通过 setState 来修改它,现在 temperature 来自 parent component,所以 TemperatureInput 无法直接控制它。

在 react 中,通常的解决方法是使 component 为 controlled,就像 <input> 元素可以接受 valueonChange props 属性,我们可以自定义使 TempratureInput 接受 temperature 和 onTemperatureChange 属性。从而当它需要更新他的 temperature 数据时,就可以通过调用 this.props.onTemperatureChange:

    handleChange(e) {
        this.props.onTemperatureChange(e.target.value);
    }

注意这里的命名时自定义的,我们可以定义任意的名称作为 component 的 props 属性名称。

onTemperatureChange 属性将会在 Calculator 中通 temperature 相关联起来,它将会修改 Calculator 中的 state 同时重新渲染两个 inputs 元素.

下面我们专注于 Calculator component,我们需要存储当前 input 的 temperatescale 数据到其 state 中,这里的 state 来自于之前从 TemperatureInput 中 lifting up 的,同时它将会同时作为两个 inputs 的可信来源,这两个 state 可以同时提供足够的数据来同时 render 两个 inputs,例如我们在 celsius input 中输入了 37,则 Calculator 的 state 应该是这样的:

{
  temperature: '37',
  scale: 'c'
}

如果我们在 Fahrenheit input 中输入了 212,则 state 应该是这样的:

{
  temperature: '212',
  scale: 'f'
}

我们不需要同时单独存储两个 input 的数据,只需要存储最后一个 input 的数据即可,scale 中记录了具体是哪个 input 的来源。最后两个 inputs 中的数据是同步的,因为他们的值都是来自于同一个 state:

class Calculator extends React.Component {
    constructor(props) {
        super(props);
        this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
        this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
        this.state = {temperature: '', scale: 'c'};
    }

    handleCelsiusChange(temperature) {
        this.setState({temperature: temperature, scale: 'c'});
    }

    handleFahrenheitChange(temperature) {
        this.setState({temperature: temperature, scale: 'f'});
    }

    render() {
        const scale = this.state.scale;
        const temperature = this.state.temperature;
        const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
        const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;

        return (
            <div>
                <TemperatureInput
                    scale='c'
                    temperature={celsius}
                    onTemperatureChange={this.handleCelsiusChange} />
                <TemperatureInput
                    scale='f'
                    temperature={fahrenheit}
                    onTemperatureChange={this.handleFahrenheitChange} />
                <BoilingVerdict celsius={parseFloat(celsius)} />
            </div>
        );
    }
}

此时无论我们在 celsius 或 Fahrenheit 中输入数据都会同时更新两个 input 的值。

此示例完整代码如下:

const React = require('react')
const ReactDOM = require('react-dom')

const BoilingVerdict = (props) => {
    if (props.celsius >= 100) {
        return <p>The Water would boil.</p>
    }
    return <p>The water would not boil.</p>
} 

const scaleNames = {
    c: 'Celsius',
    f: 'Fahrenheit'
}

const toCelsius = fahrenheit => (fahrenheit - 32) * 5 / 9;
const toFahrenheit = celsius => (celsius * 9 / 5) + 32;

const tryConvert = (temperature, convert) => {
    const input = parseFloat(temperature);
    if (Number.isNaN(input)) {
        return '';
    }
    const output = convert(input);
    const rounded = Math.round(output * 1000) / 1000;
    return rounded.toString();
}

class TemperatureInput extends React.Component {
    constructor(props) {
        super(props);

        this.handleChange = this.handleChange.bind(this);
    }

    handleChange(e) {
        this.props.onTemperatureChange(e.target.value);
    }

    render() {
        const temperature = this.props.temperature;
        const scale = this.props.scale;
        return (
            <fieldset>
                <legend>Enter temperature in {scaleNames[scale]}:</legend>
                <input
                    value={temperature}
                    onChange={this.handleChange}
                />
            </fieldset>
        );
    } 
}

class Calculator extends React.Component {
    constructor(props) {
        super(props);
        this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
        this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
        this.state = {temperature: '', scale: 'c'};
    }

    handleCelsiusChange(temperature) {
        this.setState({temperature: temperature, scale: 'c'});
    }

    handleFahrenheitChange(temperature) {
        this.setState({temperature: temperature, scale: 'f'});
    }

    render() {
        const scale = this.state.scale;
        const temperature = this.state.temperature;
        const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
        const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;

        return (
            <div>
                <TemperatureInput
                    scale='c'
                    temperature={celsius}
                    onTemperatureChange={this.handleCelsiusChange} />
                <TemperatureInput
                    scale='f'
                    temperature={fahrenheit}
                    onTemperatureChange={this.handleFahrenheitChange} />
                <BoilingVerdict celsius={parseFloat(celsius)} />
            </div>
        );
    }
}

ReactDOM.render(
    <Calculator />,
    document.getElementById('root')
);

2021-08-28T15:30:35.png

处理流程为:

  • 用户输入在任意一个 input 中输入数值后,触发 onChange 并调用 handleChange
  • 然后触发了 Calculator 的 onTemperatureChange 并将 input 的数据作为传入参数
  • 根据不同的 input 最终触发了 Calculator 中的 handleChange function 并对 state 进行了修改
  • state 改变后会触发 render,并计算最新的 celsius 和 Fahrenheit 数值
  • 最后根据计算的结果重新渲染 Temperature 的 input value
]]>
0 https://blog.niekun.net/archives/2324.html#comments https://blog.niekun.net/feed/category/web/archives/2324.html
通过 GitHub API 获取数据 https://blog.niekun.net/archives/2290.html https://blog.niekun.net/archives/2290.html Thu, 08 Apr 2021 09:44:00 +0800 admin 最近发现通过 GitHub API 可以获取到很多有用的数据,它返回一个 json 格式数据,可以后期解析得到需要的内容。

主链接:https://api.github.com/

返回内容如下:

{
  "current_user_url": "https://api.github.com/user",
  "current_user_authorizations_html_url": "https://github.com/settings/connections/applications{/client_id}",
  "authorizations_url": "https://api.github.com/authorizations",
  "code_search_url": "https://api.github.com/search/code?q={query}{&page,per_page,sort,order}",
  "commit_search_url": "https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}",
  "emails_url": "https://api.github.com/user/emails",
  "emojis_url": "https://api.github.com/emojis",
  "events_url": "https://api.github.com/events",
  "feeds_url": "https://api.github.com/feeds",
  "followers_url": "https://api.github.com/user/followers",
  "following_url": "https://api.github.com/user/following{/target}",
  "gists_url": "https://api.github.com/gists{/gist_id}",
  "hub_url": "https://api.github.com/hub",
  "issue_search_url": "https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}",
  "issues_url": "https://api.github.com/issues",
  "keys_url": "https://api.github.com/user/keys",
  "label_search_url": "https://api.github.com/search/labels?q={query}&repository_id={repository_id}{&page,per_page}",
  "notifications_url": "https://api.github.com/notifications",
  "organization_url": "https://api.github.com/orgs/{org}",
  "organization_repositories_url": "https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}",
  "organization_teams_url": "https://api.github.com/orgs/{org}/teams",
  "public_gists_url": "https://api.github.com/gists/public",
  "rate_limit_url": "https://api.github.com/rate_limit",
  "repository_url": "https://api.github.com/repos/{owner}/{repo}",
  "repository_search_url": "https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}",
  "current_user_repositories_url": "https://api.github.com/user/repos{?type,page,per_page,sort}",
  "starred_url": "https://api.github.com/user/starred{/owner}{/repo}",
  "starred_gists_url": "https://api.github.com/gists/starred",
  "user_url": "https://api.github.com/users/{user}",
  "user_organizations_url": "https://api.github.com/user/orgs",
  "user_repositories_url": "https://api.github.com/users/{user}/repos{?type,page,per_page,sort}",
  "user_search_url": "https://api.github.com/search/users?q={query}{&page,per_page,sort,order}"
}

可以通过它里面给的使用方法来获取到相应的内容,例如可以通过:https://api.github.com/users/{user} 获取到某个用户相关数据,如下是我的个人相关数据:

{
  "login": "nie11kun",
  "id": 11830603,
  "node_id": "MDQ6VXNlcjExODMwNjAz",
  "avatar_url": "https://avatars.githubusercontent.com/u/11830603?v=4",
  "gravatar_id": "",
  "url": "https://api.github.com/users/nie11kun",
  "html_url": "https://github.com/nie11kun",
  "followers_url": "https://api.github.com/users/nie11kun/followers",
  "following_url": "https://api.github.com/users/nie11kun/following{/other_user}",
  "gists_url": "https://api.github.com/users/nie11kun/gists{/gist_id}",
  "starred_url": "https://api.github.com/users/nie11kun/starred{/owner}{/repo}",
  "subscriptions_url": "https://api.github.com/users/nie11kun/subscriptions",
  "organizations_url": "https://api.github.com/users/nie11kun/orgs",
  "repos_url": "https://api.github.com/users/nie11kun/repos",
  "events_url": "https://api.github.com/users/nie11kun/events{/privacy}",
  "received_events_url": "https://api.github.com/users/nie11kun/received_events",
  "type": "User",
  "site_admin": false,
  "name": "Marco Nie",
  "company": "HJMT",
  "blog": "https://niekun.net",
  "location": "China",
  "email": null,
  "hireable": null,
  "bio": "You are the company you keep.",
  "twitter_username": null,
  "public_repos": 88,
  "public_gists": 2,
  "followers": 4,
  "following": 15,
  "created_at": "2015-04-07T06:53:16Z",
  "updated_at": "2021-03-15T08:45:12Z"
}

更多使用方法可以自己研究下。

]]>
0 https://blog.niekun.net/archives/2290.html#comments https://blog.niekun.net/feed/category/web/archives/2290.html
React 入门教程之七 -- List 和 Form https://blog.niekun.net/archives/2203.html https://blog.niekun.net/archives/2203.html Fri, 05 Mar 2021 16:13:00 +0800 admin list 列表和 key

在 JavaScript 中我们通常使用 map method 来对一个 list 的每个元素进行操作:

const numbers = [1, 2, 3, 4, 5];
const double = numbers.map((number) => { return number * 2});
console.log(double)

//output:
//[ 2, 4, 6, 8, 10 ]

在 React 中对一个 list 的元素进行操作方法类似。

我们可以在 JSX 中通过大括号{} 来建立一个 elements 的集合,下面示例中我们将 map 的返回定义为 <li> 元素并赋值给 listItems:

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) => <li>{number}</li>)

ReactDOM.render(
    <ul>{listItems}</ul>,
    document.getElementById('root')
);

注意在 render 中我们将 listItems 放在 <ul> 元素中。

通常情况下我们将 lists 放在一个 component 中:

const NumberList = (props) => {
    const numbers = props.numbers;
    const listItems = numbers.map((number) => <li>{number}</li>)
    return (
        <ul>{listItems}</ul>
    )
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
    <NumberList numbers={numbers}/>,
    document.getElementById('root')
);

当运行以上代码时,在浏览器终端会有一个 warning 警告信息:Each child in a list should have a unique "key" prop.
2021-03-05T06:50:19.png

Key 是一个特殊的 string 字符串属性需要给创建的 list element 添加的。它可以用来定位 list 中的每个元素。

下面我们给 list item 添加 Key 字符串属性:

const NumberList = (props) => {
    const numbers = props.numbers;
    const listItems = numbers.map((number) =>
        <li key={number.toString()}>
            {number}
        </li>);
    return (
        <ul>{listItems}</ul>
    );
}

添加后报警就会消除。

Keys

Key 可以帮助 React 识别哪个 item 修改过,被删除,被添加。以上示例中,我们在 map 中创建 item 时给其 key 属性,这样每个 item 可以有确切的属性值。

每个 list item 最好设置一个特殊的标识 key string 来区别于其他 items。最常用的就是使用数据中的 ID 作为 key:

const TodoItems = (props) => {
    const todos = props.todos;
    const listItems = todos.map((todo) => 
        <li key={todo.id}>
            {todo.text}
        </li>
    )
    return (
        <ul>{listItems}</ul>
    );
}
const todos = [
    {id: 1, text: '123'},
    {id: 2, text: '456'}
];
ReactDOM.render(
    <TodoItems todos={todos} />,
    document.getElementById('root')
);

当没有特定的 ID 来作为标识时,作为最后的选择,可以使用 item 的 index 作为 key:

const TodoItems = (props) => {
    const todos = props.todos;
    const listItems = todos.map((todo, index) => 
        <li key={index}>
            {todo.text}
        </li>
    )
    return (
        <ul>{listItems}</ul>
    );
}

如果 items 的顺序可能会发生变化的话,不推荐使用 index 作为 key 使用,因为可能对性能产生影响并且对 component 的 state 造成问题。如果没有定义确切的 key 给 items,React 默认会使用 index 作为 keys。

拆解 component 时 key 的处理

keys 是对应与一个数组的内容而言的,它并不能单独存在。例如我们要拆解上面的 NumberList,提取出 ListItem,则需要将 key 定义在 <ListItem /> 元素中而不是 ListItem component 内部的 <li> 中:

const ListItem = (props) => {
    return (
        <li>{props.value}</li>
    );
}

const NumberList = (props) => {
    const numbers = props.numbers;
    const listItems = numbers.map((number) =>
        <ListItem key={number.toString()} value={number}/>);
    return (
        <ul>{listItems}</ul>
    );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
    <NumberList numbers={numbers} />,
    document.getElementById('root')
);

如果写成下面模式就是错误的:

function ListItem(props) {
  const value = props.value;
  return (
    <li key={value.toString()}>
      {value}
    </li>
  );
}

每个 item 的 key 必须是特定的

数组中每个 items 使用的 key 必须是互相独立且不相同的,但并不需要在全局下互相独立。在两个单独的数组中可以,其元素可以使用相同的 key:

const React = require('react')
const ReactDOM = require('react-dom')

const Blog = (props) => {
    const sideBar = (
        <ul>
            {props.posts.map((post) =>
                <li key={post.id}>{post.title}</li>
            )}
        </ul>
    );
    const content = props.posts.map((post) =>
        <div key={post.id}>
            <h3>{post.title}</h3>
            <p>{post.content}</p>
        </div>
    );
    return (
        <div>
            {sideBar}
            <hr/>
            {content}
        </div>
    );
}

const posts = [
    {id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
    {id: 2, title: 'Installation', content: 'You can install React from npm.'}
];

ReactDOM.render(
    <Blog posts={posts} />,
    document.getElementById('root')
);

上面示例中,我们在 Blog component 中定义了两个 JSX,都创建了 list elements,每个元素的 key 使用了对应的 id 属性。在每个 list 内部 key 是互相独立的。可以看到不只是 <li> 元素可以加 key,只要通过 map 定义了一个 array 数组,就可以给每个元素加上 key 属性来互相独立识别。

key 是为了给 React 识别用的。它本身并不作为一个普通 prop 传给 components,也就是在 component 内部并不能使用这个 key 数据,如果想要在 component 中使用这个数据则需要单独定义一个其他 prop 来传入 key 数据:

const Post = (props) => {
    return (
        <li>
            {props.id}: {props.title}
        </li>
    )
}
const Blog = (props) => {
    const sideBar = (
        <ul>
            {props.posts.map((post) =>
                <Post key={post.id} id={post.id} title={post.title} />
            )}
        </ul>
    );
...
...
...
}

上面示例中,Post component 无法直接访问 key 的数据,所以我们在调用 Post 时单独定义一个 id 属性并赋值为 key 相同的数据,这样就间接的可以在 Post component 中通过 id 来读取 key 的数据。

在之前的 ListItem 示例中,我们声明了一个单独的 listItems 变量并在后续返回中将其放在 <ul> 中:

const NumberList = (props) => {
    const numbers = props.numbers;
    const listItems = numbers.map((number) =>
        <ListItem key={number.toString()} value={number}/>);
    return (
        <ul>{listItems}</ul>
    );
}

JSX 支持嵌入任何的 JavaScript 表达式,只需要使用大括号包围即可,所以上面的代码可以修改为以下模式:

    return (
        <ul>
            {numbers.map((number) =>
                <ListItem key={number.toString()} value={number} />);}
        </ul>
    );

使用哪种方式来定义 JSX 取决于对应的使用场景,总的原则是要方便与代码阅读,逻辑清晰。需要注意的是如果 map() method 中层级太复杂,可以考虑将其拆分为多个 components。

Forms 表格

HTML 的 form element 和其他 DOM elements 有点区别,因为 form element 包含有一些内部 state 数据,例如下面的 html 示例包含一个 from 表格:

<form>
    <label>
        Name:
        <input type="text" name="name">
    </label>
    <input type="submit" value="Submit">
</form>

以上示例中的 form 表格会有一个默认的 behavior 动作,那就是当用户点击 submit 按钮时会打开一个新页面。如果你不需要这个默认行为,同时需要提取 input 的信息时,需要在 submit event 事件发生时对其使用 preventDefault() method,标准的实现方法是通过 controlled components 可控构件 来处理。

controlled components

在 html 中,form 的元素如:<input>, <textarea>, 和 <select> 都有他们自己的 state 且随着用户输入信息而自动更新。在 react 中,可变的 state 存储在 component 的 state property 中且只能通过 setState() 更新。

我们可以将 from 元素的 state 和 component 的 state 合并起来作为唯一的数据来源,这样 component 既可以渲染 form 也可以控制 form 中的输入信息。一个 input 输入信息受 react component 控制的 form element 叫做 controlled component

如下示例中,我们构建一个 controlled component 来记录用户 input 的内容:

const React = require('react')
const ReactDOM = require('react-dom')

class NameForm extends React.Component {
    constructor(props) {
        super(props);
        this.state = {value: ''};

        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
    };

    handleChange(event) {
        this.setState({value: event.target.value});
    }

    handleSubmit(event) {
        alert(`a name has been submited: ${this.state.value}`);
        event.preventDefault();
    }

    render() {
        return (
            <form onSubmit={this.handleSubmit}>
                <label>
                    Name:
                    <input type="text" value={this.state.value} onChange={this.handleChange} />
                </label>
                <input type="submit" value="Submit" />
            </form>
        );
    }
}

ReactDOM.render(
    <NameForm />,
    document.getElementById('root')
);

我们将 input 的 value 属性定义为 this.state.value 的值,这里显示的永远是当前 state value 的值。当 handleChange 被触发时,会将当前用户输入的内容更新到 state value 中,然后触发 render 更新 form。

通过 controlled component 可以使 form 中 input 的内容受控于 state,这样我们就可以操作输入的数据用于其他任何地方了。

textarea 标签

在 html 中我们使用 textarea 来定义一段文本区域:

<textarea>
  Hello there, this is some text in a text area
</textarea>

在 react 中,类似于上面的 input 标签,我们将文本内容放在 value 属性中,如下示例:

const React = require('react')
const ReactDOM = require('react-dom')

class NameForm extends React.Component {
    constructor(props) {
        super(props);
        this.state = {value: 'please write some words to discribe yourself'};

        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
    };

    handleChange(event) {
        this.setState({value: event.target.value});
    }

    handleSubmit(event) {
        alert(`a discribe has been submited: ${this.state.value}`);
        event.preventDefault();
    }

    render() {
        return (
            <form onSubmit={this.handleSubmit}>
                <label>
                    TextArea:
                    <textarea type="text" value={this.state.value} onChange={this.handleChange} />
                </label>
                <input type="submit" value="Submit" />
            </form>
        );
    }
}

ReactDOM.render(
    <NameForm />,
    document.getElementById('root')
);

注意我们在构造器中给他 state value 定义了初始值,这样在第一次访问页面时就会有一段默认文字了。

select 标签

在 html 中 select 标签可以创建一个下拉菜单控件:

<select>
  <option value="grapefruit">Grapefruit</option>
  <option value="lime">Lime</option>
  <option selected value="coconut">Coconut</option>
  <option value="mango">Mango</option>
</select>

注意上面的示例中,Coconut 选项会默认选中,因为其定义了 selected 属性。在 react 中我们可以在 select 根标签中直接定义 value 属性来定义当前选中的是哪一个 option。这在 controlled component 中可以很方便的管理及更新 select element 的 value:

const React = require('react')
const ReactDOM = require('react-dom')

class NameForm extends React.Component {
    constructor(props) {
        super(props);
        this.state = {value: 'sports'};

        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
    };

    handleChange(event) {
        this.setState({value: event.target.value});
    }

    handleSubmit(event) {
        alert(`you favorite is: ${this.state.value}`);
        event.preventDefault();
    }

    render() {
        return (
            <form onSubmit={this.handleSubmit}>
                <label>
                    your favorite:
                    <select value={this.state.value} onChange={this.handleChange}>
                        <option value="sleep">Sleep</option>
                        <option value="sports">Sports</option>
                        <option value="takePhoto">Take Photo</option>
                        <option value="work">Work</option>
                    </select>
                </label>
                <input type="submit" value="Submit" />
            </form>
        );
    }
}

ReactDOM.render(
    <NameForm />,
    document.getElementById('root')
);

以上示例中,我们将 state 的 value 赋值给 select 的 value 这样 select 当前选中项总是 state 中的值,在 handleChange 触发时会更新 state 中的 value 并 render 页面。

注意我们可以给 value 赋值一个数组,这样就可以同时选中多个 option:

<select multiple={true} value={['B', 'C']}>

以上几种 form 控件起始基本结构都类似,他们都核心概念就是将元素的 value 属性和 state 挂钩,从而使 controlled component 生效。

处理多个 input 输入源

当我们需要在 component 中同时处理多个 input 元素时,可以给每个 input 添加 name 属性,然后再对应的 handle function 中通过 event.target.name 来区分他们:

const React = require('react')
const ReactDOM = require('react-dom')

class NameForm extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            isGoing: true,
            numberOfGuests: 2
        };

        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
    };

    handleChange(event) {
        const target = event.target;
        const value = target.type === 'checkbox' ? target.checked : target.value;
        const name = target.name;
        this.setState({[name]: value});
    }

    handleSubmit(event) {
        alert(`Is Going: ${this.state.isGoing}, Number Of Guests: ${this.state.numberOfGuests}`);
        event.preventDefault();
    }

    render() {
        return (
            <form onSubmit={this.handleSubmit}>
                <label>
                    Is Going:
                    <input
                    name="isGoing"
                    type="checkbox"
                    checked={this.state.isGoing}
                    onChange={this.handleChange} />
                </label>
                <br />
                <label>
                    Number of Guests:
                    <input
                    name="numberOfGuests"
                    type="number"
                    value={this.state.numberOfGuests}
                    onChange={this.handleChange} />
                </label>
                <br />
                <input type="submit" value="Submit" />
            </form>
        );
    }
}

ReactDOM.render(
    <NameForm />,
    document.getElementById('root')
);

以上示例中我们使用了 ES6 中新加入的特性: this.setState({[name]: value}); 再 object 中使用方括号 [] 来调用变量。setState() 会自动合并更新到 state 中。

input Null value

我们可以给一个 input 元素定义初值,默认情况下在页面加载完成后,input 框就可以立刻被用户进行编辑,有时候我们不希望再一开始就让用户修改 input 中的数据,此时可以临时给 input 的 value 属性赋值为 null 或 undefined 就可以了:

const React = require('react')
const ReactDOM = require('react-dom')

ReactDOM.render(
    <input value="hi" />,
    document.getElementById('root')
);

setTimeout(() => {
    ReactDOM.render(
        <input value={null} />,
        document.getElementById('root')
    );
}, 2000);

以上示例中,在页面刚加载的前 2 秒,用户无法修改默认的 hi 字符串。

]]>
0 https://blog.niekun.net/archives/2203.html#comments https://blog.niekun.net/feed/category/web/archives/2203.html
React 入门教程之六 -- Conditional Rendering https://blog.niekun.net/archives/2195.html https://blog.niekun.net/archives/2195.html Tue, 02 Mar 2021 23:10:22 +0800 admin React 中,我们可以创建独立的 component 来封装特定的功能。因此,可以根据不同的程序的 state 选择性的做部分渲染。

和 JavaScript 的相同,React 中也可以使用 conditions 语法来选择性的渲染内容。如使用 if 或 conditional operator 来根据不同 state 状态创建不同 elements 然后让 React 更新 UI 来匹配 DOM。

考虑下面两个 components:

const UserGreeting = (props) => {
    return <h1>welcome back</h1>
}

const GuestGreeting = (props) => {
    return <h1>please sign up</h1>
}

然后我们创建一个 Greeting component 来根据是否有用户登陆来显示以上两个中的一个:

const Greeting = (props) => {
    let isLoggedIn = props.isLoggedIn;
    if (isLoggedIn)
        return <UserGreeting />;
    else
        return <GuestGreeting />;
}

ReactDOM.render(
    <Greeting isLoggedIn={true} />,
    document.getElementById('root')
);

以上示例会根据 isLoggedIn 属性的值来渲染不同的内容。

elements 变量

可以使用变量存储 elements,这样可以方便的根据情况 render 部分的 component 而不需要改变输出的指令内容。

考虑下面两个 component 表示 login 和 logout:

const LoginButton = (props) => {
    return (
        <button onClick={props.onClick}>
            login
        </button>
    );
}

const LogoutButton = (props) => {
    return (
        <button onClick={props.onClick}>
            logout
        </button>
    );
}

然后我们创建 LoginControl component,它将根据当前情况渲染 login 或 logout button 以及之前创建的 Greeting element:

class LoginControl extends React.Component {
    constructor(props) {
        super(props);
        this.handleLoginClick = this.handleLoginClick.bind(this);
        this.handleLogoutClick = this.handleLogoutClick.bind(this);
        this.state = {isLoggedIn: false};
    }

    handleLoginClick() {
        this.setState({isLoggedIn: true});
    }

    handleLogoutClick() {
        this.setState({isLoggedIn: false});
    }

    render() {
        let isLoggedIn = this.state.isLoggedIn;
        let button;
        if (isLoggedIn)
            button = <LogoutButton onClick={this.handleLogoutClick} />
        else
            button = <LoginButton onClick={this.handleLoginClick} />

        return (
            <div>
                <Greeting isLoggedIn={isLoggedIn} />
                {button}
            </div>
        );
    }

}

ReactDOM.render(
    <LoginControl />,
    document.getElementById('root')
);

inline condition 单语句判断

使用 element 变量以及使用 if 语句根据条件渲染 component 是一种很好的方法。但是有时候可以使用简化语法。下面接收几种 inline condition 语法。

inline if with && operator

在 JSX 可以通过使用大括号{}来嵌入 JavaScript 表达式,包括逻辑符号:&&,在根据条件判断是否包含一个 element 时很有用。

请看下面示例:

const InlineCom = (props) => {
    return(
        <div>
            <h1>hello world</h1>
            {props.count > 10 &&
                <h2>count is: {props.count}</h2>
            }
        </div>
    );
}

ReactDOM.render(
    <InlineCom count={20} />,
    document.getElementById('root')
);

如果 props.count > 10 满足条件则后面的 element 就会成为 component 一部分。

在 JavaScript 中,true && expression 将会评估为 expression,而 false && expression 将会评估为 false。因此当 condition 为 true 时,&& 后的 element 将会输出,否则 React 将会忽略它。

inline condition operator

另一种根据情况通过 inline 单行判断来渲染 element 就是使用 JavaScript conditional operator:condition ? true : false

给 LoginControl 的返回添加如下:

        return (
            <div>
                the user is <b>{isLoggedIn ? 'currenty' : 'not'}</b> logged in.
                <Greeting isLoggedIn={isLoggedIn} />
                {button}
            </div>
        );

通过 inline conditional operator 来输出不同的信息。

也可以在较长的表达式中使用,例如可以将示例中 button 部分在 render 中这样处理:

        return (
            <div>
                the user is <b>{isLoggedIn ? 'currenty' : 'not'}</b> logged in.
                <Greeting isLoggedIn={isLoggedIn} />
                {button}
                {isLoggedIn 
                    ? <LogoutButton onClick={this.handleLogoutClick} />
                    : <LoginButton onClick={this.handleLoginClick} />
                }
            </div>
        );

使用中根据实际情况选择最合适的方式处理 condition,最终目的是为了使结构更加清晰,代码易读。注意如果判断过复杂就需要考虑拆解 component 为多个个体了。

阻止 component 渲染

某些情况下我们可能需要将一个 component 隐藏起来,即使它在别的 component 中已经渲染了。可以通过 return null 来代替它的输出。

下面示例中 WarningBanner 会根据 warn 属性的值来选择性渲染:

const React = require('react')
const ReactDOM = require('react-dom')

const WarningBanner = (props) => {
    if (!props.warn) {
        return null;
    }
    return (
        <div className='warning'>
            warning!
        </div>
    );
}

class Page extends React.Component {
    constructor(props) {
        super(props);
        this.state = {showWarning: true};
        this.handelToggleClick = this.handelToggleClick.bind(this);
    }
    handelToggleClick() {
        this.setState({showWarning: !this.state.showWarning});
    }

    render() {
        return (
            <div>
                <WarningBanner warn={this.state.showWarning} />
                <button onClick={this.handelToggleClick}>
                    {this.state.showWarning ? 'hide' : 'show'}
                </button>
            </div>
        );
    }
}

ReactDOM.render(
    <Page />,
    document.getElementById('root')
);

render method 中 return null 不会影响到 component 的 lifecycle method。例如每次更新 componentDidUpdate 依然会被自动调用。

]]>
0 https://blog.niekun.net/archives/2195.html#comments https://blog.niekun.net/feed/category/web/archives/2195.html
React 入门教程之五 -- Event https://blog.niekun.net/archives/2189.html https://blog.niekun.net/archives/2189.html Thu, 25 Feb 2021 17:23:00 +0800 admin events 处理

处理 React elements events 和处理 DOM elements 很相似,但有一些语法区别:

  • React events 命名使用 camelCase 规则,而不是 lowercase
  • 使用 JSX 传入 function 作为 events handler,而不是 string 字符串

HMTL 中处理 events 示例如下:

<button onclick="activateLasers()">
  Activate Lasers
</button>

React 中示例如下:

<button onClick={activateLasers}>
  Activate Lasers
</button>

注意它们的区别之处一个是 event 名称,一个是 handler 定义方式。

另一个区别是在 React 中不能通过 return false 的方式防止 events 的默认行为,需要明确的调用 preventDefault method 来实现。

例如在一个 html 页面中定义一个 a tag 并取消其默认打开新页面的行为,实现如下:

<a href="#" onclick="console.log('The link was clicked.'); return false">
  Click me
</a>

React 中实现同样功能代码如下:

class Link extends React.Component {
    constructor(props) {
        super(props);
        this.handleClick = this.handleClick.bind(this);
    }
    handleClick(e) {
        e.preventDefault();
        console.log('clicked me');
    }
    render() {
        return (
            <a href='#' onClick={this.handleClick}>click me</a>
        )
    }
}

e 表示 synthetic 综合的 event,当前哪个 event 触发了 e 就表示哪一个。使用 bind 绑定 的 method 在调用时会自动将 e 传入 method。下面会对 bind 是什么作出解释。

React events 同原生的 events 不完全相同,查看所有可用的 events 查看官方介绍:https://reactjs.org/docs/events.html

在 React 中一般情况下不需要通过调用 addEventListener 来给 element 添加 event listener。直接在 element 初始化时为其设置 event listener 即可。

当通过 class 来定义 component 时 event handler 一般是一个 class method,例如上面示例的 handleClick

下面的示例我们构建一个 Toggle component 可以让用户通过一个 button 来切换 ON/OFF 状态:

const React = require('react')
const ReactDOM = require('react-dom')

class Toggle extends React.Component {
    constructor(props) {
        super(props);
        this.state = { isToggleOn: true };
        this.handleClick = this.handleClick.bind(this);
    }
    handleClick() {
        this.setState({ isToggleOn: !this.state.isToggleOn });
    }
    render() {
        return (<button onClick={this.handleClick}> { this.state.isToggleOn ? 'ON' : 'OFF'} </button>);
    }
}

ReactDOM.render(<
    Toggle />,
    document.getElementById('root')
);

JSX callback 中使用 this 需要特别注意,JavaScript class 的 methods 默认是相互隔离的,如果没有主动 bind 捆绑 method 到 this,在另一个 method 中使用 this.method 会报错 undefined

如果调用 method 时不写括号() 例如:onClick={this.handleClick} 则需要提前 bind 这个 method 到 this 中,如上面的示例,bind 语法如下:

this.handleClick = this.handleClick.bind(this);

一般将其放在 constructor 中,这样初始化中就会自动执行,当然也可以在调用时直接定义:

<button onClick={this.handleClick.bind(this)}> { this.state.isToggleOn ? 'ON' : 'OFF'} </button>

如果不想使用 bind 语法来处理,那么还有两种方式来处理 class 中 methods 互相隔离这个问题。

第一种叫做 class fields syntax 语法,通过使用 arrow function 的模式定义 method,这样就可以通过通过 this.method 的方法调用 method:

    constructor(props) {
        super(props);
        this.state = { isToggleOn: true };
    }
    handleClick = () => {
        this.setState({ isToggleOn: !this.state.isToggleOn });
    }
    render() {
        return (<button onClick={this.handleClick} > { this.state.isToggleOn ? 'ON' : 'OFF'} </button>);
    }

这样就不需要在 constructor 中定义 bind 同时可以在 callback 中直接调用 this.handleClick。

但是需要注意目前这只是 React 实验性的语法,不一定保证以后会一直可用。

第二种是在 callback 中通过 arrow function 的模式调用 method:

    handleClick() {
        this.setState({ isToggleOn: !this.state.isToggleOn });
    }
    render() {
        return (<button onClick={() => this.handleClick()} > { this.state.isToggleOn ? 'ON' : 'OFF'} </button>);
    }

注意这种方法需要在 method 名称后加括号()

这种方法的缺点是当每次重新 render 渲染时都会创建新的 callback。当这个 callback 包含传给其 child component 的 props 时,可能会导致 child 重新被渲染。通常情况下推荐使用 constructor 定义 bind 或者使用 class fields syntax 语法来避免这些性能问题。

给 event handler 传入数据

有时候需要给 event handler 传入附加的参数,如下面示例 button 点击时输出一个输入数据到终端:

    handleClick(a, e) {
        this.setState({ isToggleOn: !this.state.isToggleOn });
        console.log(e._reactName);
        console.log(a);
    }
    render() {
        return (<button onClick={this.handleClick.bind(this, 'aaa')} > { this.state.isToggleOn ? 'ON' : 'OFF'} </button>);
    }

上面的示例将字符串 aaa 作为 handleClick 的传入参数,并将 bind 绑定过程直接放在 callback 中,这样就不需要在 constructor 中进行 bind 定义了。

通过 bind 绑定后会自动将 e:synthetic 综合的 event 作为第二个参数传入 function,e._reactName 返回 event 名称。

上面的示例也可以通过 arrow function 在 callback 中定义实现:

    handleClick(a, e) {
        this.setState({ isToggleOn: !this.state.isToggleOn });
        console.log(e._reactName);
        console.log(a);
    }
    render() {
        return (<button onClick={(e) => this.handleClick('aaa', e)} > { this.state.isToggleOn ? 'ON' : 'OFF'} </button>);
    }

上面的示例中 e 依然表示 synthetic event。两种方法都会将 e 作为第二个参数传入。在 arrow function 中我们可以清晰地看到数据的位置,但是通过 bind 的方式会将有些参数自动转发过去。

]]>
0 https://blog.niekun.net/archives/2189.html#comments https://blog.niekun.net/feed/category/web/archives/2189.html
React 入门教程之四 -- rendering, components 和 state https://blog.niekun.net/archives/2184.html https://blog.niekun.net/archives/2184.html Wed, 24 Feb 2021 17:17:00 +0800 admin rendering elements 渲染元素

一个 element 表示我们想要显示在屏幕上的内容:

const element = <h1>Hello, world</h1>;

不同于浏览器 DOM 中的 elements,React elements 是简单的 objects 且可以很方便的创建,React DOM 会严格的刷新 DOM 并匹配对应的 React elements。

容易混淆的概念是 component 和 element,区别是 component 是用来创建 element 的。在后续章节会介绍。

在 DOM 中渲染元素

我们的 html 页面中定义了一个 div 容器:

<div id="root"></div>

我们将其称作 root DOM 节点,因为它所有的内容都是被 React DOM 管理的。

通常情况下使用 React 创建的程序只有一个 root DOM 节点。如果你是将 React 整合到现有网站中,你可以有任意个独立的 root DOM 节点。

将 React elements 渲染到 root DOM 节点,需要通过调用 ReactDOM.render(),并将 React element 和 root DOM 节点作为传入参数:

const element = <h1>Hello, world</h1>;

ReactDOM.render(
    element,
    document.getElementById('root')
);

此时页面会显示 hello world。

刷新渲染的元素

React element 是 immutable 不可改变的,当创建了一个 element 后不可以修改其 children 或 attributes,一个 element 就好像一个视频的一帧,它表示了某一时间点的 UI。

从我们目前学到的知识,唯一刷新 UI 的方法就是重新创建新的 elements 然后调用 ReactDOM.render(),通过设置 setInterval 来定时刷新:

const tick = () => {
    const element = <h1>{new Date().toLocaleTimeString()}</h1>;

    ReactDOM.render(
        element,
        document.getElementById('root')
    );
}
setInterval(tick, 1000);

这样就会每秒钟创建一个新的 element 并通过 ReactDOM.render() 渲染到界面。

通常情况下大多数 React app 只会调用 ReactDOM.render() 一次。下一章节会介绍如何将封装到 component 中。

React 只会更新必要的内容

React DOM 会比较其当前和上一个状态的,然后只对有了变化的部分进行更新来达到最终期望的状态。

我们打开上面示例的运行页面,通过chrome 的开发工具查看 elements 情况,可以看到只有时间元素每秒在刷新:
![2021-02-24T07:44:17.png][
即使我们每秒钟都新建并渲染 element,但是只有时间文本 node 是一直通过 React DOM 在刷新的。通过以上的实验,思考我们的 UI 在某个时间点应该是什么样的,而不是只想这着去修改它。

components 和 props

components 将 UI 元素分割为独立的,可复用的片段,每个片段都是单独存在的。这一章节介绍 component 的概念,更多细节参考:React.Component

components 类似于 JavaScript 的 functions,它可以接受抽象的输入数据(props),然后返回 React elements 用来在界面上显示。

Function 和 Class Components

最简单的定义 component 方式就是定义一个 JavaScript function:

const Welcome = (props) => {
    return <h1> hello, {props.name}</h1>
}

上面的 function 是一个有效的 React component,因为它接受一个单参数 props object 作为传入数据并返回一个 React element。我们称这种 component 为 function component。

也可以使用 ES6 的 class 定义 component:

class Welcome extends React.Component {
    render() {
        return <h1>hello, {this.props.name}</h1>
    }
}

以上两种定义方式是一致的。

需要注意的是 components 名称必须是以大写字母开头,因为 React 会见以小写字母开头的 components 作为 DOM tags 标签,如:<div /> 表示一个 html div 标签。

rendering a component

上面的介绍中,我们只遇到了 DOM tags 标签类型的 React elements,例如:

const element = <div />;

elements 也可以表示用户自定义的 components:

const element = <Welcome name='marco' />

当 React 检测到使用了用户自定义的 components 它会将此 JSX 内的 attributes 或 children 作为一个 object 传入 component,这个 object 叫做 props

下面的示例会输出 hello, marco:

const Welcome = (props) => {
    return <h1> hello, {props.name}</h1>;
}

const element = <Welcome name='marco' />;
ReactDOM.render(
    element,
    document.getElementById('root')
);

以上示例过程如下:

  • 首先调用 ReactDOM.render() 渲染 <Welcome name="Sara" /> 元素.
  • React 调用 Welcome component 使用 {name: 'Sara'} 作为 props.
  • Welcome component 返回一个 <h1>Hello, Sara</h1> 元素.
  • React DOM 高效的更新 DOM 来匹配 <h1>Hello, Sara</h1> 结果.

构建 component

component 可以在其输出中引入关联其他 components。这可以让我们在一个 component 内抽象出一个多层的结构。一个 button,一个 form,一个 dialog 或者一个 screen,在 React app 中他们都统称为 components。

例如我们可以创建一个 App component 来渲染多个 Welcome component:

const Welcome = (props) => {
    return <h1> hello, {props.name}</h1>;
}

const App = () => {
    return (
        <div>
            <Welcome name='marco' />
            <Welcome name='tim' />
            <Welcome name='jone' />
        </div>
    )
}
ReactDOM.render(
    <App />,
    document.getElementById('root')
);

以上示例中,没有定义 App 的 props,因为不需要给其传入数据,也是可以的。

一般情况下,新建的 React app 只有一个顶层的 App component。

拆解 component

不要害怕将一个 component 拆解为多个小 components。例如下面这个 Comment component:

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <img className="Avatar"
          src={props.author.avatarUrl}
          alt={props.author.name}
        />
        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

它的 props 包含一个 author object,一个 text,一个 data,描述了一个社交网站上一个 commit 的内容。

修改这个 component 有点困难,因为它有很多的嵌套,同时也难以复用它的内部组件。下面我们尝试拆解这个 component。

首先我们拆解出 Avatar

const Avatar = (props) => {
    return (
        <img className="Avatar"
            src={props.user.avatarUrl}
            alt={props.user.name}
        />
    );
}

Avatar 并不需要知道它被用于 commit 中,因此我们修改其 prop 名称为一个更加通用的:user。推荐从 component 本身为出发点命名 props,而不是考虑什么地方使用它。

现在我们可以简化 Commit component:

function Comment(props) {
    return (
        <div className="Comment">
            <div className="UserInfo">
                <Avatar user={props.author} />
                <div className="UserInfo-name">
                    {props.author.name}
                </div>
            </div>
            <div className="Comment-text">
                {props.text}
            </div>
            <div className="Comment-date">
                {formatDate(props.date)}
            </div>
        </div>
    );
}

我们将 props.author 作为 user 数据传入 Avatar component 中。

下面我们拆解 UserInfo,其中包含一个 Avatar component:

const UserInfo = (props) => {
    return (
        <div className="UserInfo">
            <Avatar user={props.user} />
            <div className="UserInfo-name">
                {props.user.name}
            </div>
        </div>
    );
}

然后进一步简化 Commit:

function Comment(props) {
    return (
        <div className="Comment">
            <UserInfo user={props.user} />
            <div className="Comment-text">
                {props.text}
            </div>
            <div className="Comment-date">
                {formatDate(props.date)}
            </div>
        </div>
    );
}

拆解 component 在开始看起来使工作量变大了,但是在稍微复杂写的 app 中我们就能够利用这些可复用的 components。一条基本准则是:如果 UI 中的某一部分被多次使用,如 button,panel,Avatar等,或者其自身结构比较复杂,如:App, FeedStory, Comment 等,将他们拆解为独立 components 是一个好的选项。

props 是只读的

当把一个 component 定义为 function 或 class 时,需要注意的是不可以修改 props 的值。

考虑下面的 function:

function sum(a, b) {
  return a + b;
}

以上的 function 被称作 pure 纯粹的,以为它没有尝试修改输入数据。

作为对比,下面的就是 impure 不纯粹的 function,因为会尝试修改它的输入数据:

function sum(a, b) {
  a = b;
}

React 程序有一条限制条件:所有的 React components 都需要是 pure function 来对待 props 数据

当然应用程序的 UI 是随时间动态变化的。下一节我们会介绍 state 的概念。通过 state 可以使 React components 在运行期间修改它们的输出 elements 来响应用户动作,网络响应等。同时不违反上面的那条规则。

state 和 lifecycle

这一节介绍 React components 中 state 和 lifecycle 的概念。

在前一章的示例中,我们通过一个 tick function 在指定时间间隔通过创建新 element 并渲染的方式刷新 UI:

const tick = () => {
    const element = (
        <div>
            <h1>hello world</h1>
            <h2>{new Date().toLocaleTimeString()}</h2>
        </div>
    );

    ReactDOM.render(
        element,
        document.getElementById('root')
    );
}
setInterval(tick, 1000);

下面我们介绍通过创建一个封装好的 Clock component,设置定时器并更新其自身。

首先我们根据上面的示例创建 Clock component:

const Clock = (props) => {
    <div>
        <h1>hello world</h1>
        <h2>{props.date.toLocaleTimeString()}</h2>
    </div>
}

const tick = () => {
    ReactDOM.render(
        <Clock date={new Date()} />,
        document.getElementById('root')
    );
}
setInterval(tick, 1000);

tick 调用 Clock component 并定义 date prop 的数据供 Clock 使用。但是上面的实现缺乏一个基本需求,那就是 Clock 应该在其自身中定义定时器并每秒刷新数据的。

我们想要在渲染时达到如下效果调用 Clock:

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

为了实现上述功能,需要为 Clock 添加 statestate 类似于 props 但是它是由 component 私有且完全控制的。

首先我们需要将 component 转换为 class 模式,转换过程如下:

  • 首先创建一个 ES6 class,且继承自 React.Component
  • 添加一个 render() function,将原 component function 的返回元素放入其返回值中
  • render() 中用 this.props 代替 props
class Clock extends React.Component {
    render() {
        return (
            <div>
                <h1>hello world</h1>
                <h2>{this.props.date.toLocaleTimeString()}</h2>
            </div>
        );
    }
}

当 update 更新发生时会自动调用 render function。但当我们将 <Clock /> 放入 DOM 后,将只会有一个 Clock object 实例被使用,这就让我们可以使用 statelifecycle 等功能。

添加 state

下面我们将 date 数据直接放入 CLock component 中。

首先将 render 中的 this.props.date 修改为 this.state.date

class Clock extends React.Component {
    render() {
        return (
            <div>
                <h1>hello world</h1>
                <h2>{this.state.date.toLocaleTimeString()}</h2>
            </div>
        );
    }
}

然后添加 class constructor 构造器给 this.state 赋初值:

class Clock extends React.Component {
    constructor(props) {
        super(props);
        this.state = {date: new Date()}
    }
    render() {
        return (
            <div>
                <h1>hello world</h1>
                <h2>{this.state.date.toLocaleTimeString()}</h2>
            </div>
        );
    }
}

注意 class component 总是应该使用 constructor 且初始化参数为 props。child class 有 constructor 时需要调用 super 来初始化 parent class,具体语法参考我的 JavaScript 教程:https://blog.niekun.net/archives/2011.html

然后删除渲染到 DOM 中 Clockdate prop,以及我们设置的 setInterval 定时器:

ReactDOM.render(
    <Clock />,
    document.getElementById('root')
);

修改完成后的完整代码如下:

class Clock extends React.Component {
    constructor(props) {
        super(props);
        this.state = {date: new Date()}
    }
    render() {
        return (
            <div>
                <h1>hello world</h1>
                <h2>{this.state.date.toLocaleTimeString()}</h2>
            </div>
        );
    }
}

ReactDOM.render(
    <Clock />,
    document.getElementById('root')
);

下面我们实现 Clock 设置自己的定时器并每秒更新。

添加 lifecycle method

对于包含很多 components 的程序,但某个 component 不再需要是需要及时释放其占用的资源。

我们需要 Clock 第一次在 DOM 中渲染时设置一个 timer 定时器,在 React 中叫做 mounting 载入。同时我们需要当 Clock 在 DOM 中被删除时清除这个定时器,在 React 中叫做 unmounting 卸载。

我们可以在 components 载入或载出时通过定义特殊的 method 来运行特定指令:

  componentDidMount() {
  }

  componentWillUnmount() {
  }

这些 methods 叫做 lifecycle methods

componentDidMount method 会在 component 第一次输出到 DOM 后被自动调用,我们可以将定时器定义在这里:

    componentDidMount() {
        this.timerID = setInterval(() => this.tick(), 1000);
    }

这样当 Clock 渲染到 UI 后会自动启动这个定时器。注意使用 this 定义的参数可以在 class 中任意地方被调用。

注意 setInterval 中定义的响应动作需要写在 callback 内 () => {} 中,不要直接写:setInterval(this.tick, 1000)。因为如果要在 callback 调用 method 需要在 constructor 中做如下定义:

this.tick = this.tick.bind(this);

componentWillUnmount method 会在 component 将要被删除时自动调用,我们将定时器在这里取消:

    componentWillUnmount() {
        clearInterval(this.timerID);
    }

接下来我们定义每秒都会自动运行的 tick method,通过 this.setState() 来更新本地 state 中的设置:

    tick() {
        this.setState({ date: new Date() });
    }

最终的完整代码如下:

const React = require('react')
const ReactDOM = require('react-dom')

class Clock extends React.Component {
    constructor(props) {
        super(props);
        this.state = { date: new Date() }
    }

    componentDidMount() {
        this.timerID = setInterval(() => this.tick(), 1000);
    }

    componentWillUnmount() {
        clearInterval(this.timerID);
    }

    tick() {
        this.setState({ date: new Date() });
    }

    render() {
        return (
            <div>
                <h1>hello world</h1>
                <h2>{this.state.date.toLocaleTimeString()}</h2>
            </div>
        );
    }
}

ReactDOM.render(
    <Clock />,
    document.getElementById('root')
);

现在整个处理流程如下:

  • ReactDOM.render() 传入 <Clock /> 后,React 调用 Clock 构造器初始化 state 为一个包含 date 的 object
  • 然后 React 调用 Clock 的 render() 查询到需要显示的 UI 元素,然后更新 DOM 以匹配 Clock 的输出
  • 当 Clock 的输出嵌入到 DOM 后会调用 componentDidMount,Clock 告诉浏览器设置一个定时器每秒调用 tick()
  • 每秒钟调用一次 tick(),这里面 Clock component 通过 setState() 配置了其 UI 更新任务,通过 setState() React 就知道了 state 发生了变化并再次调用 render() 监测需要显示的内容,此时的 this.state.date 和上一次的发生了变化,React 就会更新 DOM 到最新的状态。
  • 当 Clock 从 DOM 中删除后,React 会调用 componentWillUnmount 并结束定时器

正确使用 state

关于 state 的使用需要如下的几点要求。

第一点,不要直接修改 state

下面的语法不会触发重新 render 渲染 component:

this.state.comment = 'Hello';

正确的语法为使用 setState()

this.setState({comment: 'Hello'});

唯一可以对 state 赋值的是在 constructor 构造器中。

第二点,state 更新是异步的

React 为了性能可能会在一次 component update 更新中捆绑多个 setState() 调用,由于 this.propsthis.state 可能会被异步更新,所以不要依赖他们的数据来计算后续的 state

如下示例可能会错误的更新 counter:

this.setState({
  counter: this.state.counter + this.props.increment,
});

为了实现是这个需求,使用 setState 的另一种格式:传入一个 function,第一个参数为当前 state,第二个参数为 props 然后内部计算 state 更新:

this.setState(function (state, props) {
    return {
        counter: state.counter + props.increment
    };
});

第三点,state 更新会合并

当调用 setState() 后,会合并设置的 object 到当前 state 中。

如下示例,state 可能包含多个独立的变量:

    constructor(props) {
        super(props);
        this.state = {
            posts: [],
            comments: []
        };
    }

然后我们可以单独调用 setState() 来分别更新它们:

    componentDidMount() {
        fetchPosts().then(response => {
            this.setState({
                posts: response.posts
            });
        });

        fetchComments().then(response => {
            this.setState({
                comments: response.comments
            });
        });
    }

合并过程是自动完成的,所以通过 setState 修改 comments 只会更新 comments 而不会改变 posts。

数据向下传递

一个 component 的 child 或 parent 都不会知道当前 component 是包含 state 还是不包含,且不关心是通过 function 还是 class 方式构建的 component。所以 state 被认为是封装的不能够被外界所访问。

component 的 state 可以作为 props 向它的 child component 传递:

<FormattedDate date={this.state.date} />

如上所示 FormattedDate 可以接受 date prop,它并不知道数据来自 parent 的 state 还是 props:

function FormattedDate(props) {
  return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}

通常这叫做 top-downunidirectional 数据流。任何 state 都被某个特定 component 所有,state 的数据只能在其 component 的 child 中传递出去。

我们通过建立 App component 并构建三个 Clock component 来展示 component 之间是互相独立的:

function App() {
    return (
        <div>
            <Clock />
            <Clock />
            <Clock />
        </div>
    );
}

ReactDOM.render(
    <App />,
    document.getElementById('root')
);

每个 Clock 都有个各自的定时器并独立更新。

在 React 中,components 定义为 stateful 还是 stateless 的取决于其在运行中可能的变化,可以在 stateful 的 component 中使用 stateless 的 component,反过来亦可。

]]>
0 https://blog.niekun.net/archives/2184.html#comments https://blog.niekun.net/feed/category/web/archives/2184.html
React 入门教程之三 -- 介绍 JSX https://blog.niekun.net/archives/2183.html https://blog.niekun.net/archives/2183.html Wed, 24 Feb 2021 14:45:12 +0800 admin 请看下面的一个定义:

const element = <h1>Hello, world!</h1>;

它既不是一个 string 也不是 html。它叫做 JSX,是一种对 JavaScript 语法的扩展。推荐在 React 中使用 JSX 来描述 UI,它用来创建 React 类型的 elements 然后将其在 DOM 中渲染。

下面对 JSX 做一些基本介绍。

使用 JSX 的原因

React 认同这种观点:渲染逻辑结构和 UI 逻辑结构是原生相互耦合的,events 的处理,状态的变化,数据何时显示等。不同于人为的将 markup 部分和 logic 逻辑部分放在单独的文件,React 的耦合单元叫做 components 可以同时包含 markup 和 logic 部分。

React 并不强制要求使用 JSX,但是大部分开发者认为它可以帮助在 JavaScript 中处理 UI 的问题。它也可以帮助 React 显示更多的 error 或 warning 信息。

JSX 中使用 JavaScript 表达式

下面的示例中,我们声明一个变量并在 JSX 中通过大括号{} 调用变量:

const name = 'marco nie';
const element = <h1>hello, {name}</h1>

ReactDOM.render(
    element,
    document.getElementById('root')
);

我们可以将任意的 JavaScript 表达式通过大括号{}放如 JSX 中。例如:1 + 1user.name 都是有效的 JavaScript 表达式:

const city = 'han zhong';
const user = {
    name: 'marco',
    age: 20
}
const element = <h1>hello, {user.name}, from {city}, time: {1 + 20}</h1>

可以将 JSX 分割成多行以提高代码可读性,需要使用小括号包围起来,防止编译器错误的自动添加分行符:

const element = (
    <h1>
        hello, {user.name}, from {city}, time: {1 + 20}
    </h1>
);

编译后 JSX 表达式会转换为通用的 JavaScript function 或 objects。这就意味着我们可以在 if 或 for 表达式中,传入数据中或者 返回值中使用 JSX:

const testFuc = () => {
    return <h1>hello, {user.name}</h1>
}

JSX 中定义属性

可以使用引号 "" 将一个字符串作为属性:

const el1 = <div tabIndex="0"></div>

可以以通过大括号{} 将一个 JavaScript 表达式嵌入属性:

const el2 = <img src={user.name}></img>

当使用 JavaScript 嵌入属性时不能在大括号中使用引号,同一个属性中只能使用引号嵌入字符串或者大括号嵌入 JavaScript 表达式中的一种。

由于 JSX 更加接近于 JavaScript 而不是 html,所以 React 中的 DOM 使用 camelCase 属性命名规范代替 html 中的属性名称,例如 html 元素的 class 属性在 JSX 中定义为 className,tabindex 在 JSX 中为 tabIndex。

定义子元素

如果一个 tag 元素内容是空的则可以使用 /> 立刻结束定义:

const el3 = <img src=''/>

JSX tag 标签内也可以有子元素:

const el4 = (
    <div>
        <h1>hello,</h1>
        <h2>i am your friend</h2>
    </div>
);

注意只能在一个元素内定义子元素,不能直接定义两个同级的元素否则会报错。

防止 injection 注入攻击

在 JSX 中嵌入用户输入是安全的:

const title = response.potentiallyMaliciousInput;
// This is safe:
const element = <h1>{title}</h1>;

默认情况下 React 在渲染前会将脱开所有嵌入 JSX 中的数据,因此可以确保不会注入任何没有明确定义在应用中的数据。渲染前所有的所有内容都会转换为 string 字符串形式。则能够防止 XSS (cross-site-scripting) 攻击。

JSX 表达 objects

Babel 会将 JSX 向下编译为对 React.createElement() 的调用,以下两种定义方法是相同的:

const el5 = (
    <h1 className='test'>
        hello world
    </h1>
);
const el6 = React.createElement(
    'h1',
    {className: 'test'},
    'hello world'
)

通过 React.createElement() 创建元素会额外做一些语法检查来防止错误代码。但通常我们使用下面语法创建一个 object:

const el7 = {
    type: 'h1',
    props: {
        className: 'test',
        children: 'hello world'
    }
}

以上方式创建的 object 叫做 React elements。React 使用这些 objects 来构建 DOM 并及时更新。

推荐代码编辑器使用 Babel 语法定义环境,这样可以同时支持 ES6 和 JSX 语法结构。设置方法参考:https://babeljs.io/docs/en/editors

]]>
0 https://blog.niekun.net/archives/2183.html#comments https://blog.niekun.net/feed/category/web/archives/2183.html