美文网首页
学习笔记

学习笔记

作者: Chasingcar_501 | 来源:发表于2019-01-10 11:30 被阅读0次
1.条件渲染

  必须谨慎对待JSX回调函数中的this,类的默认方法是不会绑定this的。如果你忘记绑定this.handleClick并把它传入onClick,当你调用这个函数的时候this值会是undefined。
这并不是React的特殊行为;它是函数如何在js中运行的一部分。通常情况下,如果你没有在方法后添加(),例如onClick={this.handleClick},你应该为这个方法绑定(bind)this。

2.条件渲染
image.png

实现目标:点击Login后显示“Welcome back”,按钮变为Logout。
拆分成两个大组件Greeting和Button,Greeting组件根据isLoggedIn参数是否为true分别显示函数组件UserGreeting(Welcome back!)、GuestGreeting(Please sign up)

return.png

疑问:

function LoginButton(props) {
  return (
    <button onClick={props.onClick}>
      Login
    </button>
  )
}
1.为什么要带参数props
2.onClick里面为什么是props.onClick

可以用if语句、JSX表达式、逻辑与&&、三目运算符等进行条件渲染

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

在 JavaScript 中,true && expression 总是返回 expression,而 false && expression 总是返回 false。如果条件是 true,&& 右侧的元素就会被渲染,如果是 false,React 会忽略并跳过它。

image.png

  如下,组件的 render 方法返回 null 并不会影响该组件生命周期方法的回调。例如,componentWillUpdate 和 componentDidUpdate 依然可以被调用。

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(() => ({
      showWarning:  ! this.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')
);
3.列表&keys

在js中如何转化列表,如下,将数组中的每一项翻倍

const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);//注意此处需要number参数,仅仅是一个标识
console.log(doubled);

可以使用{}在JSX内构建一个元素集合

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

这段代码生成了一个1到5的数字列表.

keys

keys可以在DOM中的某些元素被增加或删除时帮助React识别哪些元素发生了变化。因此你应当给数组中的每一个元素赋予一个确定的标识。
元素的key最好是这个元素在列表中的一个独一无二的字符串,通常使用数据id,如果没有id可以使用它的序列号索引index作为key

const todoItems = todos.map((todo, index) =>
  <li key={index}>  //或者<li key={todo.id}>
    {todo.text} 
  </li>
);

如果需要给列表重新排序,不建议用索引排序,因为这会导致渲染变得很慢。

用keys提取组件

元素的key只有在它和它的兄弟节点对比时才有意义。比方说,如果你提取出一个ListItem组件,你应该把key保存在数组中的这个<ListItem />元素上,而不是放在ListItem组件中的<li>元素上。并不是说不能放在li元素,下面的Blog例子就可以
正确使用方法:

function ListItem(props) {
  return <li> {props.value}</li>
}
function NumberList(props) {
  const numbers = props.numbers;
  const ListItems = numbers.map((number) => 
    <ListItem key={number.toString()} value={number}/>
  );
  return (
    /* 此处要把ListItem包在ul里,不能在最后的ReactDOM.render中包一层ul,
     因为React里不能把对象作为一个孩子节点,如果要render多个孩子,使用数组或用对象把孩子包起来.
    这也解释了为什么要定义这两个函数组件,一个生成li一个包装li
    */
     <ul>
      {ListItems}
     </ul> 
  );
}

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

当在map()方法内部调用元素时,最好为每一个元素加上独一无二的key。

元素的key在它的兄弟元素之间应该唯一

数组元素中使用的key在其兄弟之间应该是独一无二的。然而,它们不需要是全局唯一的。当我们生成两个不同的数组时,我们可以使用相同的键。

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

const post = [
  {id:1,title:"Hello World",content:"Welcome to learn React"},
  {id:2,title:"Installation",content:"You can install React from npm"}
];
ReactDOM.render(
  <Blog post={post} />,
  document.getElementById('root')
);

key会作为给React的提示,但是不会传递给你的组件。如果需要使用key,请将其作为属性传递。

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

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

在JSX中嵌入map()

在上面例子中,我们声明了一个单独的ListItem变量并将其包含在JSX中,JSX运行在大括号中嵌入任何表达式,可以在map()中这样使用:

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

      )}
    </ul>
  );
}

这么做有时会让代码清晰,有时会造成滥用。何时为了可读性提取出组件完全取决于你,但是如果一个map()嵌套了太多层,可能就是提取组件的一个好机会。

4.表单

HTML表单与React中其他DOM元素不同,因为表单元素生来就保留一些内部状态。例如下面这个表单只接受一个唯一的name

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

当用户提交表单时,HTML的默认行为会使这个表单跳转到一个新页面。React中也是如此,大多数情况下,我们会构造一个处理提交表单并可访问用户输入表单数据的函数。实现这一点的标准方法是使用“受控组价”。

受控组件:
<input>和<select>都要绑定一个change事件,每当表单发生变化时,都会被写入组件的state中,这种组件在React中被称为受控组件。在受控组件中,组件渲染出的状态与它的value或者checked prop相对应。React通过这种方式消除了组件的局部状态,使得应用整个状态可控。React受控组件更新State的流程如下:
1)可以通过初始state中设置表单的默认值
2)每当表单值发生变化时,调用onChange事件处理器
3)事件处理器通过合成实践对象e拿到改变后的状态,并更新state
4)setState触发视图的重新渲染,完成表单组件值的更新
React中的数据是单向流动的,表单的数据来源于组件的state,并通过props传入,这也成为单向数据绑定。然后通过onChange事件处理器将新的表单数据写回到state,完成了双向数据绑定。

我们通过使react变成一种单一数据源的状态来结合二者。React负责渲染表单的组件仍然控制用户后续输入时所发生的变化。相应的,其值由React控制的输入表单元素称为“受控组件”。

在HTML当中,像<input>,<textarea>, 和 <select>这类表单元素会维持自身状态,并根据用户输入进行更新。但在React中,可变的状态通常保存在组件的状态属性中,并且只能用 setState() 方法进行更新。
我们通过使React变成一种单一数据源的状态来结合二者。React负责渲染表单的组件仍然控制用户后续输入时发生的变化。
例如,我们想要使上个例子中在提交表单时输出name,我们可以写成“受控组件”的形式:

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 was submitted: ' + 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>
    );
  }
}

使用”受控组件”,每个状态的改变都有一个与之相关的处理函数。这样就可以直接修改或验证用户输入。例如,我们如果想限制输入全部是大写字母,我们可以将handleChange 写为如下:

handleChange(event) {
  this.setState({value: event.target.value.toUpperCase()});
}
textarea标签

React中,<textarea>会用value属性代替,这样表单中的<textarea>类似于使用单行输入的表单。

class EssayForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: 'Please write an essay about your favorite DOM element.'
    };

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

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

  handleSubmit(event) {
    alert('An essay was submitted: ' + this.state.value);
    event.preventDefault();
  }

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

注意this.state.value是在构造函数中初始化,这样文本区域就能获取到其中的文本。

select标签

在React中并不使用selected属性,而是在根select标签上用value属性表示选中项。这在受控组件中更为方便,因为只需要在一个地方更新组件。

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

  handleSubmit(event) {
    alert('Your favorite flavor is: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Pick your favorite La Croix flavor:
          <select value={this.state.value} onChange={this.handleChange}>
            <option value="grapefruit">Grapefruit</option>
            <option value="lime">Lime</option>
            <option value="coconut">Coconut</option>
            <option value="mango">Mango</option>
          </select>
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

总之,<input type="text">, <textarea>, 和 <select> 都十分类似 - 他们都通过传入一个value属性来实现对组件的控制。

多个输入的解决方案

当你有处理多个受控的input元素时,你可以通过给每个元素添加一个name属性,来让处理函数根据 event.target.name的值来选择做什么。也就是用一个控制函数管理多个input框内容的改变。

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.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;

    this.setState({
      [name]: value
    });
  }

  render() {
    return (
      <form>
        <label>
          Is going:
          <input
            name="isGoing"
            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          Number of guests:
          <input
            name="numberOfGuests"
            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}
5.状态提升

几个组件共用状态数据时,最好把这部分共享的状态提升至离它们最近的父组件中管理。
例子:温度计算器判断水是否会在指定温度下烧开

为什么App.js里没有名为App的组件, 在index.js里import App from './App';最后ReactDOM.render(<App />, document.getElementById('root'));也没出错??console.log(App);是函数对象

function BoilingVerdict(props) {
  if (props.celsius >= 100) {
    return <p>水会烧开</p>;
  }
  return <p>水不会烧开</p>;
}

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

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

  render() {
    const temperature = this.state.temperature;
    return (
      <fieldset>
        <legend>输入一个摄氏温度</legend>
        <input
          value={temperature}
          onChange={this.handleChange} />

        <BoilingVerdict
          celsius={parseFloat(temperature)} />

      </fieldset>
    );
  }
}

export default Calculator;

legend 元素为 [fieldset 元素]定义标题(caption)。

添加第二个输入框

在输入摄氏温度的基础上,再提供一个华氏温度,并且他们能保持同步。

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

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

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

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

Tip: const rounded = Math.round(output * 1000) / 1000;取小数点后三位小数
Math.round四舍五入

先看一个其他状态提升的小例子:
image.png
class Child_1 extends React.Component {
  render() {
    return (
      <div>
        <h1>{this.props.txt+"1"}</h1>   //引可不加 因为this.props.txt是字符串
      </div>
    )
  }
}
class Child_2 extends React.Component {
  // constructor(props) {  //构造函数可要可不要
  //  super(props);
  // }
  render() {
    return (
      <div>
        <h1>{this.props.txt+2}</h1>
      </div>
    )
  }
}

class Child extends React.Component {
  constructor(props) {
    super(props) ;
    this.state = {txt:'hello'};
    this.handleChange = this.handleChange.bind(this);
  }
    handleChange (e) {
      this.setState({txt:e.target.value});
    }
    render(){
      return (
        <div>
     //input里的value可不要 不影响输出
          <input type="text" onChange={this.handleChange} value={this.state.txt}/> 
          <h1>{this.state.txt}</h1>
          <Child_1 txt={this.state.txt}/>
          <Child_2 txt={this.state.txt}/>
        </div>
      )
    }
}

export default Child;

isNaN和Number.isNaN: 在ES5中判断一个变量是否为NaN采用isNaN(),但是对于特殊变量,这个方法就出现了不符合预期的结果:
isNaN(NaN) //true
isNaN(undefined) //true
isNaN('qwer') //true
isNaN(123) //false
在ES6中推出了新的方法Number.isNaN(),符合预期:
Number.isNaN(undefined) //false
Number.isNaN(NaN) //true
Number.isNaN('qwer') //false
Number.isNaN(123) //false
全局方法isNaN()会先将参数转为Number 类型,在判断是否为NaN ,所以在类型转换失败或运算错误时值为NaN,返回true,其他全为false;Number.isNaN()先判断参数类型,在参数为Number的前提下运算错误时值为NaN返回true,其他全为false
ps:NaN是一个number类型
isNaN("1"); // fales "1" 被转化为数字 1,因此返回false
isNaN("SegmentFault"); // true "SegmentFault" 被转化成数字 NaN
Number.isNaN('0/0') //string not number false
isNaN('0/0') //arithmethic ilegal (NaN) true
Number.isNaN('123') //string not number false
isNaN('123') //convert to number false
Number.isNaN('Hello') //string not number false
isNaN('Hello') //convert fail(NaN) true

回到温度的例子,两个输入框要联动,要是组件之间双向绑定没试过行不行?
这样单一数据源从上至下的流动是官方推荐的写法。

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

function toCelsius(fahrenheit) {
  return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
  return (celsius * 9 / 5) + 32;
}
function 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 BoilingVerdict(props) {
  if(props.celsius >= 100) {
    return <p>The water would boil.</p>;
  }
  return <p>The water would not boil.</p>;
}


class TemperatureInput extends React.Component{
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }
  handleChange(e) {
    //所有的this.props.属性都是由父组件提供的
    this.props.onTemperatureChange(e.target.value);
  }
  render() {
    //此处temperature不管是c还是f,传给我什么我展示什么。通用组件
    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);
    //什么叫状态提升至父组件呢?把tmperature在父组件的state里定义,这样每次更新该值就是在父组件里
    this.state = {temperature:'',scale:'c'};
  }
  handleCelsiusChange(temperature) {
    //在这里更新输入的temperature
    this.setState({scale:'c',temperature})
  }

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

  render() {
    const scale = this.state.scale;
    const temperature = this.state.temperature;
    //scale属性用来判断在哪个输入框输入数字,再转换出另一个框的数字
    const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
    //在这里将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')
);

疑问:<input/>里的value是为了获取还是为了传递?

5.组合 vs 继承

React官方建议使用组合而非继承实现代码的复用
以下介绍几个新手常用继承解决的问题,看看使用组合的解决方案


 alert(`Welcome aboard ${this.state.login}`); //不能使用“”,要用``才能识别里面的this.state.login

相关文章

网友评论

      本文标题:学习笔记

      本文链接:https://www.haomeiwen.com/subject/sebgrqtx.html