React入门

React简介

React是一个用于构建用户界面的JavaScript库,主要有以下几个特点:

  • 声明式设计--React采用声明范式,可以轻松描述应用
  • 高效--React通过对DOM的模拟,最大限度地减少与DOM的交互
  • 灵活--React可以与已知的库或框架很好地配合
  • JSX--JSX是JavaScript语法的扩展。React开发不一定使用JSX,但建议使用
  • 组件--通过React构建组件,是的代码更加容易得到复用,能够更好地应用在大项目的开发中
  • 单向响应的数据流--React实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统的数据绑定更简单

React安装

  • 使用create-react-app快速构建React开发环境

创建React App是开始构建新的React单页应用程序的最佳方式。它设置您的开发环境,以便您可以使用最新的JavaScript功能,提供一个很好的开发人员体验,并优化您的应用程序的生产。

npm install -g create-react-app
create-react-app hello-world
cd hello-world
npm start

ReactDOM.render()

ReactDOM.render 是 React 的最基本方法,用于将模板转为 HTML 语言,并插入指定的 DOM 节点。

ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.getElementById('example')
);

React JSX

JSX是一个看起来很像XML的JavaScript语法扩展,我们不是必须使用JSX,但是JSX有以下优点:

  • JSX执行更快,因为它在编译为JavaScript代码后进行优化
  • 它是类型安全的,在编译过程中就能发现错误
  • 使用JSX编写模板更加方便快捷

JSX的基本语法规则:<u>遇到 HTML 标签(以 < 开头),就用 HTML 规则解析;遇到代码块(以 { 开头),就用 JavaScript 规则解析。</u>

警告:

由于JSX比HTML更接近JavaScript,React DOM使用camelCase属性命名约定而不是HTML属性名称。

例如,class成为className在JSX,和tabindextabIndex

/**使用JXS */

function formatName(user) {
  return user.firstName + ' ' + user.lastName;
}
const user = {
  firstName: 'Harper',
  lastName: 'Perez'
};
const element = (
  <h1>
  Hello, {formatName(user)}!
  </h1>
);

React组件和属性

React允许将代码封装成组件,然后像插入普通的HTML标签一样,在网页中插入这个组件,如下实例:

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

function App() {
  return (
    <div>
      <Welcome name="Sara" />
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
    </div>
  );
}

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

);

上面的代码中,变量App就是一个组件,所有的组件类都必须有自己的render方法,用于输出组件。

注意:组件名称必须使用大写,比如<div />就是一个HTML标签,而<App />表示一个组件,并且要求组件类只能有一个顶层标签,否则会报错。

组件的属性可以在组件类的 this.props 对象上获取,上面的例子中,name属性是通过this.props.name来获取的。

注意:在添加属性时, class 属性需要写成 className ,for 属性需要写成 htmlFor ,这是因为 class 和 for 是 JavaScript 的保留字。

this.props.children

this.props 对象的属性与组件的属性一一对应,但是有一个例外,就是 this.props.children 属性。它表示组件的所有子节点。

var NotesList = React.createClass({
  render: function() {
    return (
      <ol>
      {
        React.Children.map(this.props.children, function (child) {
          return <li>{child}</li>;
        })
      }
      </ol>
    );
  }
});

ReactDOM.render(
  <NotesList>
    <span>hello</span>
    <span>world</span>
  </NotesList>,
  document.body
);

上面代码的 NoteList 组件有两个 span 子节点,它们都可以通过 this.props.children 读取,运行结果如下。

result.png

这里需要注意, this.props.children 的值有三种可能:如果当前组件没有子节点,它就是 undefined ;如果有一个子节点,数据类型是 object ;如果有多个子节点,数据类型就是 array 。所以,处理 this.props.children 的时候要小心。

React 提供一个工具方法 React.Children 来处理 this.props.children 。我们可以用 React.Children.map 来遍历子节点,而不用担心 this.props.children 的数据类型是 undefined 还是 object。更多的 React.Children 的方法,请参考官方文档

组件状态和生命周期

我们通常使用两种数据来控制一个组件:props和state。props是在父组件中指定的,而且一经指定,在被指定的组建的生命周期中则不改变,对于需要改变的数据,我们需要使用state来处理。

通常情况下,我们需要在constructor中初始化state,然后在需要修改的时候调用setState()方法。

/**加了状态之后的定时器 */
class Clock extends Component {
    constructor(props) {
        super(props);
        this.state = {date: new Date()}; // 设置初始状态
    }

     componentDidMount() {
       this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }
  componentWillUnmount() {      //在组件从 DOM 中移除的时候立刻被调用
    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>
    );
  }
}

上面的例子,我们主要是通过以下几步来实现的:

1)当<Clock />传递给ReactDOM.render()React时,React调用组件的Clock构造函数。由于Clock需要显示当前时间,它this.state用包括当前时间的对象初始化。我们稍后将更新此状态。

2)React然后调用Clock组件的render()方法。这是React如何了解应该在屏幕上显示什么。React然后更新DOM以匹配Clock渲染输出。

3)当Clock输出插入到DOM中时,React调用componentDidMount()生命周期钩子。在它内部,Clock组件要求浏览器设置一个定时器,tick()每秒钟调用一次。

4)每秒浏览器调用该tick()方法。在其中,Clock组件通过setState()使用包含当前时间的对象调用来计划UI更新。由于setState()调用,React知道状态已更改,并render()再次调用方法来了解应该在屏幕上显示什么。这一次,this.state.daterender()方法中会有所不同,因此渲染输出将包括更新的时间。React相应地更新DOM。

5)如果Clock组件从DOM中删除,React会调用componentWillUnmount()生命周期钩子,以便定时器停止。

如何正确使用状态

不要直接修改状态

例如,这将不会重新渲染组件:

// Wrong
this.state.comment = 'Hello';

相反,请使用setState()

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

唯一可以指定this.state值的地方是构造函数。

状态更新可能会异步

React可以将多个setState()调用批处理为单个更新以实现性能。

因为this.propsthis.state可能异步更新,你不应该依赖它们的值来计算下一个状态。

例如,此代码可能无法更新计数器:

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

要解决它,使用setState()接受函数而不是对象的第二种形式。该函数将接收先前的状态作为第一个参数,并将应用更新时的props作为第二个参数:

// Correct
this.setState((prevState, props) => ({
  counter: prevState.counter + props.increment
}));

我们使用上面的箭头函数,但它也可以与常规函数一起使用:

// Correct
this.setState(function(prevState, props) {
  return {
    counter: prevState.counter + props.increment
  };
});
状态更新已合并

每次调用setState()时,React都会将您提供的对象合并到当前状态。

例如,您的状态可能包含几个独立变量:

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

合并很浅,因为this.setState({comments})保留this.state.posts不变,只是完全替换this.state.comments

组件的生命周期

组件的生命周期可分成三个状态:

  • Mounting: 已插入真实DOM
  • Updating: 正在被重新渲染
  • Unmounting: 已移出真实DOM

生命周期的方法:(详细说明,可以参考官方文档。)

  • componentWillMount 在渲染前调用。
  • componentDidMount : 在第一次渲染后调用。之后组件已经生成了对应的DOM结构,可以通过this.getDOMNode()来进行访问。 如果你想和其他JavaScript框架一起使用,可以在这个方法中调用setTimeout, setInterval或者发送AJAX请求等操作(防止异部操作阻塞UI)。
  • componentWillReceiveProps 在组件接收到一个新的prop时被调用。这个方法在初始化render时不会被调用。
  • shouldComponentUpdate 返回一个布尔值。在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用。
    可以在你确认不需要更新组件时使用。
  • componentWillUpdate在组件接收到新的props或者state但还没有render时被调用。在初始化时不会被调用。
  • componentDidUpdate 在组件完成更新后立即调用。在初始化时不会被调用。
  • componentWillUnmount在组件从 DOM 中移除的时候立刻被调用。

处理事件与条件渲染

处理事件

使用React元素处理事件与处理DOM元素上的事件非常相似。有一些语法差异:

  • React事件使用camelCase命名,而不是小写命名。
  • 使用JSX你传递一个函数作为事件处理程序,而不是一个字符串。

另一个区别是,你不能返回false以防止React中的默认行为。您必须preventDefault明确调用。

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // This binding is necessary to make `this` work in the callback
    this.handleClick = this.handleClick.bind(this);
  }

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

  render() {
    return (
      <button onClick={this.handleClick}>  // 传递一个函数作为事件处理程序
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

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

你必须注意this在JSX回调中的意思。在JavaScript中,类方法默认不绑定。如果你忘记绑定this.handleClick并把它传递给onClick,在函数实际被调用时this将会报undefined的错误。

条件渲染

React中的条件渲染与JavaScript中的条件工作方式相同。使用类似ifJavaScript运算符条件运算符来创建表示当前状态的元素,并让React更新UI以匹配它们。

/**条件渲染 */

class LoginControl extends 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 = null;
    if (isLoggedIn) {
      button = <LogoutButton onClick={this.handleLogoutClick} />;
    } else {
      button = <LoginButton onClick={this.handleLoginClick} />;
    }

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

function LoginButton(props) {
  return (
    <button onClick={props.onClick}>
      Login
    </button>
  );
}

function LogoutButton(props) {
  return (
    <button onClick={props.onClick}>
      Logout
    </button>
  );
}

function UserGreeting(props) {
  return <h1>Welcome back!</h1>;
}

function GuestGreeting(props) {
  return <h1>Please sign up.</h1>;
}

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

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

虽然声明变量和使用if语句是有条件地呈现组件的一种很好的方式,但有时您可能想使用较短的语法。有几种方法来内联JSX中的条件,如下所述。

逻辑运算符

您可以在JSX嵌入任何表达式,将它们括在大括号中。这包括JavaScript逻辑&&运算符。它可以方便地有条件地包括一个元素:

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总是解析为expressionfalse && expression总是解析为false

因此,如果条件是true,紧接着的元素&&将出现在输出中。如果是false,React将忽略并跳过它。

带条件运算符的If-else

用于有条件地内联元素的另一种方法是使用JavaScript条件运算符condition ? case1 : case2,当条件为true时,执行case1,否则执行case2。

防止组件呈现

在极少数情况下,您可能希望组件隐藏自身,即使它由另一个组件呈现。要做这个返回null而不是其渲染输出。

在下面的例子中,根据<WarningBanner />被调用的prop的值来渲染warn。如果prop的值为false,则组件不呈现:

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(prevState => ({
      showWarning: !prevState.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')
);

列表和Key值

列表

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

上述实例显示1到5之间的数字的项目符号列表。但是运行此代码时,将会收到一条警告,指出应为列表项提供一个键。“key”是创建元素列表时需要包含的特殊字符串属性。如下:

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <li key={number.toString()}>    //为列表项指定key值
      {number}
    </li>
  );
  return (
    <ul>{listItems}</ul>
  );
}

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

Key值

key帮助确定哪些项目已更改,已添加或已删除。应该给数组中的元素赋予key以给元素一个稳定的身份。

选择key的最好方法是使用一个字符串,它在其兄弟之间唯一标识一个列表项。通常,您会使用数据中的ID作为键:

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

使用键提取组件:

如果提取一个ListItem组件,则应该将该key保存<ListItem />在数组中的元素上,而不是<li>ListItem本身的根元素上。

正确使用:

function ListItem(props) {
  // Correct! There is no need to specify the key here:
  return <li>{props.value}</li>;
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // Correct! Key should be specified inside the array.
    <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')
);

一个好的经验法则就是map()调用中的元素需要Key值。

key值在兄弟姐妹中唯一

数组中使用的key在其兄弟之间应该是唯一的。但是,它们不需要是全局唯一的。当我们生成两个不同的数组时,我们可以使用相同的key:

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

运行结果:

result2.png

表单

用户在表单填入的内容,属于用户跟组件的互动,所以不能用 this.props 读取。

/**表单 */
class NameForm extends Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

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

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

  handleSumbit(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>
    )
  }
}

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

上面代码中,文本输入框的值,不能用 this.props.value 读取,而要定义一个 onChange 事件的回调函数,通过 event.target.value 读取用户输入的值。textarea 元素、select元素、radio元素都属于这种情况,更多介绍请参考官方文档

参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容

  • 现在最热门的前端框架,毫无疑问是React。在基于React的React Native发布一天之内,就获得了 50...
    Mycro阅读 997评论 3 6
  • 作者:阮一峰原文地址:http://www.ruanyifeng.com/blog/2015/03/react.h...
    IT程序狮阅读 1,084评论 0 16
  • 在React这股目前最热前端框架之风刮来之前,一直在Cocos2d-html5游戏和半路出家的Android应用的...
    hahafei阅读 358评论 0 2
  • 小白一枚,觉得阮一峰老师写的react入门非常简单易懂,所以就转载过来了。等到我自己后面能力变强之后,我就自己写一...
    LU7IN阅读 285评论 0 0
  • 首先要从小孩的兴趣出发 其次要重视小孩的利益 最后,要用温和的方式说,用不可动摇的态度来做。
    此身越重洋l阅读 97评论 0 0