React文档阅读

React

一、核心概念
1、JSX: 在JSX中可以在{}中放置任何有效的JS表达式(变量 + 函数 + …)
const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>
const h2 = <div>{1 + 1}</div>

编译后,JSX表达式会被转化为普通的JS函数调用;
JSX里的class变成了className;

React DOM 在渲染所有输入内容之前,默认会进行转义;确保在应用中,永远不会注入非自身明确编写的内容;内容在渲染之前被转换成字符串,可以防止XSS(cross-site-scripting 跨站脚本)攻击;

Babel会把JSX转译成一个名为React.createElement()函数调用;
下面两种代码是等效的:

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

React.createElement()会预先执行一些检查,实际上它创建了一个这样的对象

// 注意:这是简化过的结构
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  }
};
2、元素渲染

React元素是不可变对象;一旦被创建,无法更改子元素或者属性,它代表某个特定时刻的UI;

更新UI的唯一方式是创建一个全新的元素,并将其传入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);

React 只更新它需要更新的部分,React DOM 会将元素和它的子元素与它们之前的状态进行比较,并且只会进行必要的更新来使DOM达到预期的状态;

问题?需要将element中所有的元素都挨个比较吗?

使用了定时器,尽管每一秒都要新建一个描述整个UI树的元素,React DOM只会更新实际改变的内容。

3、组件&Props(组件名字必须以大写字母开头)

React会将以小写字母开头的组件视为原声DOM标签。

- 函数式组件:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

- 类式组件:

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

- React自定义组件:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

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

页面上可以渲染 Hello,Sara

注意⚠️:所有React组件必须像纯函数一样保护props不被更改;

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

这样的函数被称为“纯函数”,因为该函数不会尝试更改入参,且多次调用下相同的入参始终返回相同的结果。

function withdraw(account, amount) {
  account.total -= amount;
}

这样的函数则不是“纯函数”,因为它更改了自己的入参;

4、state & 生命周期

state与props类似,但是state是私有的,完全受控于当前组件。

使用类式组件:

  • 当Clock组件第一次被渲染到DOM中的时候 (“挂载 mount”),就为其设置一个计时器;
  • 当DOM中Clock组件被删除的时候 (“卸载 unmount”),应该清除计时器;
class Clock extends React.Component {
  constructor(props) {
    super(props); // 继承父类的变量,将props传递到父类的构造函数中
    this.state = {date: new Date()}; // 使用this.state赋初值
  }
  
  // 组件已经被渲染到DOM后运行
  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>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

// 父组件
ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);
  • Clock 组件的输出被插入到DOM中,React就会调用ComponentDidMount生命周期方法。此方法中,Clock组件向浏览器请求设置一个计时器每秒调用一次组件的tick()方法。

  • tick()方法中,Clock组件通过调用setState()来计划进行一次UI更新。React知道state已经发生变化,就会重新调用render()方法来确定页面上该显示什么

一定要注意:State的更新可能是异步的;

出于性能考虑,React可能会把多个setState()调用合并成一个调用;

this.propsthis.state可能会异步更新,所以不要依赖它们的值来更新下一个状态。比如,下面这段代码可能会无法更新计数器。

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

解决办法:

setState()接收一个函数而不是一个对象,这个函数用上一个state作为第一个参数,将此次更新被应用时的props作为第二个参数;(使用箭头函数和普通函数都可以)

// Correct
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));
一定要注意:State的更新会被合并;

调用setState()的时候,React会将你提供的对象合并到当前的state;

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

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

这里的合并是浅合并,所以this.setState({comments})完整保留了this.state.posts,但是完全替换了this.state.comments

5、事件处理

React元素的事件处理和DOM元素的很相似,但在语法上有些不同:

  • React事件的命名采用小驼峰式(camelCase),而不是纯小写;
  • 使用JSX语法时,你需要传入一个函数作为事件处理函数,而不是一个字符串;

传统的HTML:

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

React:

<button onClick={activateLasers}> // onClick中的Click首字母大写
  Activate Lasers
</button>
  • React不能通过返回false的方式阻止默认行为;必须显式的使用preventDefault;

HTML:

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

React:

function ActionLink() {
  function handleClick(e) {
    e.preventDefault(); // 阻止默认行为
    console.log('The link was clicked.');
  }

  return (
    <a href="#" onClick={handleClick}>
      Click me
    </a>
  );
}
  • 当使用React时,你一般不需要使用addEventListener为已创建的DOM元素添加监听器;使用ES6 class语法定义组件时,通常做法:将事件处理函数声明为class中的方法。
class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // 为了在回调中使用 `this`,这个绑定是必不可少的
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(state => ({
      isToggleOn: !state.isToggleOn
    }));
  }

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

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

在JS中,class的方法默认不会绑定this,如果忘记绑定this.handleClick,并把它传入了onClick,当调用这个函数的时候,this的值为undefined,这样的话,this.setState()方法不可用。

如果觉得bind很麻烦,有两种方法可以解决;

方法一: 使用实验性的public class fields语法,可以使用class fields正确的绑定回调函数。

class LoggingButton extends React.Component {
  // 此语法确保 `handleClick` 内的 `this` 已被绑定。
  // 注意: 这是 *实验性* 语法。
  handleClick = () => {
    console.log('this is:', this);
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}

方法二: 如果没有使用class fields语法,可以在回调中使用箭头函数:(该回调函数作为prop传入子组件时,这些组件可能会进行额外的重新渲染,通常建议在构造器中给绑定或使用class fields语法来避免这类性能问题)

class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    // 此语法确保 `handleClick` 内的 `this` 已被绑定。
    return (
      <button onClick={() => this.handleClick()}>
        Click me
      </button>
    );
  }
}
向事件处理程序传递参数

在循环中,通常会为事件处理函数传递额外的参数,例如:若id是要删除那一行的ID,以下两种方式都可以向事件处理函数传递参数:

<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

这两种情况下,React的事件对象e会被作为第二个参数传递;如果通过箭头函数的方式,事件对象必须显示的进行传递;通过bind的方式,事件对象以及更多的参数将会被隐式的进行传递

6、条件渲染
  • 可以使用逻辑与(&&):
    在JS中,true && expression 总是会返回 expression, 而 false && expression 总是会返回 false
  • 可以使用三目运算符:
    condition ? true : false
阻止组件渲染:

让render方法直接返回null,那么组件不会渲染;但是不会影响组件的生命周期。

function WarningBanner(props) {
  if (!props.warn) {
    return null;
  }

  return (
    <div className="warning">
      Warning!
    </div>
  );
}
7、列表 & Key

注意:使用list时,一定要在每一项元素中添加一个key;
key帮助React识别哪些元素改变了。比如:被添加或者被删除。
一个元素的key最好是这个元素在列表中拥有的独一无二的字符串;

一般来说,使用数据中的id来作为元素的key,当元素没有确定id的时候,万不得已,可以使用元素索引index来作为key;(如果列表项目顺序可能会发生变化,不建议使用索引来当key值)。

经验: 在map()方法中的元素上设置key属性。key只是在兄弟节点之间必须唯一;但不需要全局唯一;生成两个不同的数组时,可以使用相同的key值。

注意: key会传递信息给React,但不会传递给组件;如果组件中需要使用key属性的值,应当使用其他属性名显式传递这个值。

const content = posts.map((post) =>
  <Post
    key={post.id}
    id={post.id}
    title={post.title} />
);

上面例子中,Post 组件可以读出 props.id,但是不能读出 props.key

8、表单
  • 受控组件
    在 HTML 中,表单元素(如、 和 )之类的表单元素通常自己维护 state,并根据用户输入进行更新。
    而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新。

渲染表单的React组件还控制着用户输入过程中表单发生的操作;被React以这种方式控制取值的表单输入元素就叫做 “受控组件”。

在表单元素上设置value属性,因此显示的值始终为this.state.value,这使得React的state称为唯一数据源。

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

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

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

  handleSubmit(event) {
    alert('提交的名字: ' + this.state.value);
    event.preventDefault();
  }

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

select标签

class FlavorForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: 'coconut'};
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

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

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          选择你喜欢的风味:
          <select value={this.state.value} onChange={this.handleChange}>
            <option value="grapefruit">葡萄柚</option>
            <option value="lime">酸橙</option>
            <option value="coconut">椰子</option>
          </select>
        </label>
        <input type="submit" value="提交" />
      </form>
    );
  }
}

文件input标签

在 HTML 中, 允许用户从存储设备中选择一个或多个文件,将其上传到服务器,或通过使用 JavaScript 的 File API 进行控制。

<input type="file" />

文件标签的 value 只读,所以它是 React 中的一个非受控组件。

处理多个输入时,可以给每个元素添加name属性;

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

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

  handleInputChange(event) {
    const target = event.target;
    const value = target.name === 'isGoing' ? target.checked : target.value;
    const name = target.name;

    this.setState({
      [name]: value // 注意:是[name],不是name
    });
    // 等同于
    // var partialState = {};
	// partialState[name] = value;
	// this.setState(partialState);
  }

  render() {
    return (
      <form>
        <label>
          参与:
          <input
            name="isGoing"
            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          来宾人数:
          <input
            name="numberOfGuests"
            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}

受控组件指定value的prop会阻止用户更改输入,如果指定了value,仍然可以编辑,很有可能是将value设置为undefined或者null

ReactDOM.render(<input value="hi" />, mountNode);

setTimeout(function() {
  ReactDOM.render(<input value={null} />, mountNode);
}, 1000);
9、状态提升(props是只读的)
function tryConvert(temperature, convert) {
  const input = parseFloat(temperature);
  if (Number.isNaN(input)) { // 判断参数是否严格等于NaN
    return '';
  }
  const output = convert(input);
  const rounded = Math.round(output * 1000) / 1000; 
  // 保留三位小数并四舍五入,round 四舍五入
  return rounded.toString();
}

在 React 应用中,任何可变数据应当只有一个相对应的唯一“数据源”。通常,state 都是首先添加到需要渲染数据的组件中去。然后,如果其他组件也需要这个 state,那么你可以将它提升至这些组件的最近共同父组件中。你应当依靠自上而下的数据流,而不是尝试在不同组件间同步 state。

10、组合 VS 继承
  • 组合
    有些组件无法提前知道它们子组件的具体内容;在Sidebar(侧边栏)Dialog(对话框)等展现通用容器(box)的组件中特别容易遇到这种情况。建议使用特殊的children prop来将它们的子组件传递到渲染结果中:(和Vue中的slot有异曲同工之妙)
function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}

<FancyBorder>JSX 标签中的所有内容都会作为一个 children prop 传递给 FancyBorder 组件。因为 FancyBorder 将 {props.children}渲染在一个 <div>中,被传递的这些子组件最终都会出现在输出结果中。

function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        Welcome
      </h1>
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}

少数情况下,可能需要在一个组件中预留多个“洞”;这种情况下,可以不是用children,而是自行约定:将所需内容传入props,并使用相应的prop。

function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left}  // 使用left
      </div>
      <div className="SplitPane-right">
        {props.right} // 使用right
      </div>
    </div>
  );
}
function App() {
  return (
    <SplitPane
      left={
        <Contacts />
      }
      right={
        <Chat />
      } />
  );
}
  • 继承
    暂时并没有发现需要使用继承来构建组件层次的情况。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值