React 基础

React 是什么

简介

React 是一个声明式,高效且灵活的用于构建用户界面的 Javascript 库。使用 React 可以将一些简短、独立的代码片段组合成复杂的 UI 界面,这些代码库被称为“组件”。

UI = render(data) => 单向数据流

MVC & MVVM & MVP

这是 3 种常见的软件架构设计模式,都是通过分离关注点的方式来组织代码结构,优化开发效率。

  • MVC:通过 Model-View-Controller储存页面的业务数据及对应数据的操作-显示逻辑-前两者的纽带,事件触发器)来组织代码结构。

  • MVP:通过Model-View-Presenter,MVC 在逻辑复杂会造成代码混乱,Presenter 是 Controller 的改良。MVP 模式中,View 层的接口暴露给了 Presenter,因此我们可以在 Presenter 中将 Model 和 View 的变化绑定在一起,以此来实现 View 和 Model 的同步更新。这样就实现了对 View 和 Model 的解耦,Presenter 还包含了其他的响应逻辑。

  • MVVM:Model-View-ViewModel,MVC 的改进版,和 MVP 的思想相同,不过它是通过双向数据绑定,将 View 和 Model 的同步更新自动化了。当 Model 发生变化时,ViewModel 就会自动更新;ViewModel 更新了,View 也会更新。这样将 presenter 的工作自动化了。在 Vue 中,是通过数据劫持和发布订阅者模式来实现这个功能的。

JSX 模板语法

INFO

JSX 是 Javascript 的语法扩展,运用于 React 中,格式像模板语言,事实上在 Javascript 内部实现。元素是构成 React 应用的最小单位,JSX 是声明 React 中的元素。

JSX 可使用引号来定义以字符串为值的属性:const element = <div tabIndex="0"></div>

可使用大括号来定义以JavaScript表达式为值的属性:const element = <img src={user.avatarUrl} />

  • 因为 JSX 语法上更接近 JS 而不是 HTML,所以使用 camelCase(小驼峰命名)来定义属性的名称; JSX 里的 class 变成了 className,而 tabindex 则变为 tabIndex
  • 没有子节点的React元素可以用 /> 结束;
  • 为什么jsx里要写html?JSX 是 JavaScript XML 的简写,表示在 JavaScript 代码中写XML(HTML)格式的代码。声明式语法更加直观,与HTML结构相同,降低了学习成本、提升开发效率。JSX 是 React 的核心内容。

JSX 支持的表达式

JSX 支持表达式、变量、方法名。

// 变量
const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;
function formatName(user) {
  return user.firstName + ' ' + user.lastName;
}

// 方法
const user = {
  firstName: 'Harper',
  lastName: 'Perez'
};

const element = (
  <h1>
    Hello, {formatName(user)}!
  </h1>
);

function getGreeting(user) {
  if (user) {
    return <h1>Hello, {formatName(user)}!</h1>;
  }
  return <h1>Hello, Stranger.</h1>;
}

JSX 指定属性

❓这一块不太懂

const element = <img src={user.avatarUrl}></img>;

注意:JSX支持防注入(防止XSS攻击)
const title = response.potentiallyMaliciousInput;  // 此时只是字符串
// 直接使用是安全的: const element = <h1>{title}</h1>;

React 如何预防XSS

// 反射型 XSS

https://xxx.com/search?query=userInput

// 服务器在对此 URL 的响应中回显提供的搜索词:query=123
<p>您搜索的是: 123</p>

// https://xxx.com/search?query=<img src="empty.png" onerror ="alert('xss')">
<p>您搜索的是: <img src="empty.png" onerror ="alert('xss')"></p>
// 如果有用户请求攻击者的 URL ,则攻击者提供的脚本将在用户的浏览器中执行。

  
// 存储型 XSS,存储到目标数据库
// 评论输入,所有访问用户都能看到了
<textarea>
  <img src="empty.png" onerror ="alert('xss')">
</textarea>
  
// 部分源码
for (index = match.index; index < str.length; index++) {
  switch (str.charCodeAt(index)) {
    case 34: // "
      escape = '&quot;';
      break;
    case 38: // &
      escape = '&amp;';
      break;
    case 39: // '
      escape = '&#x27;';
      break;
    case 60: // <
      escape = '&lt;';
      break;
    case 62: // >
      escape = '&gt;';
      break;
    default:
      continue;
  }
}

// 一段恶意代码
<img src="empty.png" onerror ="alert('xss')"> 
// React 在渲染到浏览器前进行的转义,可以看到对浏览器有特殊含义的字符都被转义了
// 恶意代码在渲染到 HTML 前都被转成了字符串
&lt;img src=&quot;empty.png&quot; onerror =&quot;alert(&#x27;xss&#x27;)&quot;&gt; 
  
// JSX
const element = (
  <h1 className="greeting">
      Hello, world!
  </h1>
);
  
// 通过 babel 编译后的代码
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);
  
// React.createElement() 方法返回的 ReactElement
const element = {
  $$typeof: Symbol('react.element'),
  type: 'h1',
  key: null,
  props: {
    children: 'Hello, world!',
        className: 'greeting'   
  }
  ...
}
 
// 如何模拟一个Children会如何?
const storedData = {
    "ref":null,
    "type":"body",
    "props":{
        "dangerouslySetInnerHTML":{
            "__html":"<img src=\"empty.png\" onerror =\"alert('xss')\"/>"
        }
    }
};
// 转成 JSON
const parsedData = JSON.parse(storedData);
// 将数据渲染到页面
render () {
    return <span> {parsedData} </span>; 
}
  
// $$typeof 是用来标记一个ReactElement的,JSON化后Symbol会丢失,React会报错

JSX 表示对象

const element = (
  <h1 className="greeting"> //区别于html语法中的class,jsx中是className
    Hello, world!
  </h1>
);
// 等同于React.createElement
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  }
}

将 JSX 渲染成 DOM

// 使用ReactDOM.render
const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));
// render只能代表当前时刻的状态
// 更新元素 只能再次 ReactDOM.render
function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(element, document.getElementById('root')); 
}
setInterval(tick, 1000); // 不建议多次render

JSX 转成 JS

JSX 可以当作语法糖,在 babel 官网中尝试open in new window


// 安装babel 及react 的依赖
npm install core-js @babel/core @babel/preset-env @babel/
preset-react @babel/register babel-loader @babel/
plugin-transform-runtime --save-dev

.babelrc
{
    "presets" : [ 
        "@babel/preset-env" ,
        "@babel/preset-es2015",
        "@babel/preset-react"
    ],
    "plugins" : [
        "@babel/plugin-transform-runtime"
    ]
}

props 及 state

组件

组件,类似于 Javascript 中的函数,接受任意入参(props),并返回用于描述页面内容的 React 元素。有函数式组件和 Class 类组件。

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

🦝tips:此刻我才明白之前有个老师说 React 更接近于函数式编程的思想。且 React 的核心是 JSX = Javascript XML,JSX 在 Javascript 内部实现。

  • 渲染组件

    function Welcome(props) {
    return <h1>Hello, {props.name}</h1>;
    }
    
    const element = <Welcome name="Sara" />;
    ReactDOM.render(
      element,
      document.getElementById('root')
    );
    
    // 自定义组件使用大写字母开头
    import React from 'react';
    
    // 正确!组件需要以大写字母开头:
    function Hello(props) {
      // 正确! 这种 <div> 的使用是合法的,因为 div 是一个有效的 HTML 标签:
      return <div>Hello {props.toWhat}</div>;
    }
    
    function HelloWorld() {
      // 正确!React 知道 <Hello /> 是一个组件,因为它是大写字母开头的:
      return <Hello toWhat="World" />;
    }
    
  • 组件的拆分和组合

❓这一块不太懂

// 页面内多次引用
<div>
  <Welcome name="Sara" />
  <Welcome name="Cahal" />
  <Welcome name="Edite" />
</div>

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>
  );
}

// 拆分后为
function Comment(props) {
  return (
    <div className="Comment">
      <UserInfo user={props.author} />
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}
  • 受控组件与非受控组件:

    1️⃣受控组件:组件状态只能由用户控制而非代码。

    在HTML的表单元素中,它们通常自己维护一套state,并随着用户的输入自己进行UI上的更新,这种行为是不被我们程序所管控的。而如果将React里的state属性和表单元素的值建立依赖关系,再通过onChange事件与setState()结合更新state属性,就能达到控制用户输入过程中表单发生的操作。被React以这种方式控制取值的表单输入元素就叫做受控组件。

    // input自身维护的状态,外界无法获取数据
    class TestComponent extends React.Component {
      render () {
        return <input name="username" />
      }
    }
    
    // 可以设置初始值
    class TestComponent extends React.Component {
      constructor (props) {
        super(props);
        this.state = { username: 'test' };
      }
      render () {
        return <input name="username" value={this.state.username} />
      }
    }
    
    // 可以读取并设置初始值
    class TestComponent extends React.Component {
      constructor (props) {
        super(props);
        this.state = {
          username: "test"
        }
      }
      onChange (e) {
        console.log(e.target.value);
        this.setState({
          username: e.target.value
        })
      }
      render () {
        return <input name="username" value={this.state.username} onChange={(e) => this.onChange(e)} />
      }
    

    2️⃣非受控组件:组件状态只能由代码控制而非用户。

    // 如果不想关心表单元素的值是如何变化的,只想取值,可以使用ref
    import React, { Component } from 'react';
    
    export class UnControll extends Component {
      constructor (props) {
        super(props);
        this.inputRef = React.createRef();
      }
      handleSubmit = (e) => {
        console.log('我们可以获得input内的值为', this.inputRef.current.value);
        e.preventDefault();
      }
      render () {
        return (
          <form onSubmit={e => this.handleSubmit(e)}>
            <input defaultValue="lindaidai" ref={this.inputRef} />
            <input type="submit" value="提交" />
          </form>
        )
      }
    }
    

props

所有的 React 组件必须像纯函数一样保护他们的 props 不被更改。

// 错误,要像纯函数一样幂等
function withdraw(account, amount) {
  account.total -= amount;
}

state

需补充

生命周期

事件处理

语法格式

  1. 在JSX元素上添加事件,通过on*EventType这种内联方式添加,命名采用小驼峰式(camelCase)的形式,而不是纯小写(原生HTML中对DOM元素绑定事件,事件类型是小写的);
  2. 无需调用addEventListener进行事件监听,也无需考虑兼容性,React已经封装好了一些的事件类型属性;
  3. 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串;
  4. 不能通过返回 false 的方式阻止默认行为。你必须显式的使用 preventDefault;

接收参数

  1. 事件对象 e 会被作为第二个参数传递;
  2. 通过箭头函数的方式,事件对象必须显式的进行传递;
  3. 通过 Function.prototype.bind 的方式,事件对象以及更多的参数将会被隐式的进行传递;
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

条件渲染

if else

class LoginControl extends React.Component {
  constructor(props) {
    super(props);
    this.handleLoginClick = this.handleLoginClick.bind(this);
    this.handleLogoutClick = this.handleLogoutClick.bind(this);
    this.state = {isLoggedIn: false};
  }
  handleLoginClick() {
    this.setState({isLoggedIn: true});
  }
  handleLogoutClick() {
    this.setState({isLoggedIn: false});
  }
  render() {
    const isLoggedIn = this.state.isLoggedIn;
    let button;
    if (isLoggedIn) {
      button = <LogoutButton onClick={this.handleLogoutClick} />;
    } else {
      button = <LoginButton onClick={this.handleLoginClick} />;
    }
    return (
      <div>
        <Greeting isLoggedIn={isLoggedIn} />
        {button}
      </div>
    );
  }
}
ReactDOM.render(
  <LoginControl />,
  document.getElementById('root')
);

&& 与运算符

function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 &&
        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      }
    </div>
  );
}
const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
  <Mailbox unreadMessages={messages} />,
  document.getElementById('root')
);
// 返回false的表达式,会跳过元素,但会返回该表达式
render() {
  const count = 0;
  return (
    <div>
      { count && <h1>Messages: {count}</h1>}
    </div>
  );
}

三元运算符

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      {isLoggedIn
        ? <LogoutButton onClick={this.handleLogoutClick} />
        : <LoginButton onClick={this.handleLoginClick} />
      }
    </div>
  );
}

如何阻止组件渲染

function 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.handleToggleClick = this.handleToggleClick.bind(this);
  }

  handleToggleClick() {
    this.setState(state => ({
      showWarning: !state.showWarning
    }));
  }

  render() {
    return (
      <div>
        <WarningBanner warn={this.state.showWarning} />
        <button onClick={this.handleToggleClick}>
          {this.state.showWarning ? 'Hide' : 'Show'}
        </button>
      </div>
    );
  }
}

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

列表

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

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);
// 若没有key,会warning a key should be provided for list items
// key可以帮助react diff,最好不用index作为key,会导致性能变差;
// 如果不指定显式的 key 值,默认使用索引用作为列表项目的 key 值;

create-react-app

INFO

官网open in new window,create-react-app是一个官方支持的创建React单页应用程序的脚手架。它提供了一个零配置的现代化配置设置。

immutable & immer

immutable

官网open in new window

Facebook 工程师 Lee Byron 花费 3 年时间打造,与 React 同期出现,但没有被默认放到 React 工具集里(React 提供了简化的 Helper)。它内部实现了一套完整的 Persistent Data Structure,还有很多易用的数据类型。像 Collection、List、Map、Set、Record、Seq。有非常全面的map、filter、groupBy、reduce``find函数式操作方法。同时 API 也尽量与 Object 或 Array 类似。

immer

官网open in new window

  • currentState:被操作对象的最初状态
  • draftState:根据 currentState 生成的草稿状态,它是 currentState 的代理,对 draftState 所做的任何修改都将被记录并用于生成 nextState 。在此过程中,currentState 将不受影响
  • nextState:根据 draftState 生成的最终状态
  • produce:用来生成 nextState 或 producer 的函数
  • producer:通过 produce 生成,用来生产 nextState ,每次执行相同的操作
  • recipe:用来操作 draftState 的函数