本文采用 es6 语法,彻底参考 https://reactjs.org/docs/
本文彻底参考 React 官方 Quick Start 部分,除了最后的 thinking-in-react 小节
首先你须要点击安装 nodejs(npm)。而后执行:javascript
npm install -g create-react-app
若是上述命令执行失败能够运行如下命令:html
npm install -g create-react-app --registry=https://registry.npm.taobao.org
而后创建一个 react 并运行:java
create-react-app myApp cd myApp npm start
这样你就简单的完成了一个 react app 创建,其目录结构以下( 图中不包括 node_modules 目录,下同 ):node
咱们删除一些没必要要的东西,而后修改目录结构以下(不能删 node_modules 目录,若是删了就在项目目录下运行 npm i
就行了):react
其中 components 是个目录。程序员
修改 index.js 以下:es6
import React from 'react'; import ReactDOM from 'react-dom'; ReactDOM.render( <h1> hello world! </h1>, document.getElementById('root') );
而后命令行运行:npm
npm start
你就能够看到熟悉的 'hello world' 了数组
JSX 是 react 中容许 js 和 html 混写的语法格式,须要依赖 babel 编译。这里我就只研究它的语法:浏览器
const element = <h1>Hello, world!</h1>;
能够经过花括号在其中插入表达式:
function formatName(user){ return user.firstName + ' ' + user.lastName; } const user = { firstName: 'Harper', lastName: 'Perez' }; const element = ( <h1> Hello, {formatName(user)}! </h1> ); ReactDOM.render( element, document.getElementById('root') );
能够将 HTML 语句写为多行以增长可读性,用小括号括起来能够防止自动插入分号致使的错误。
JSX 也是个表达式,因此能够用在 for 和 if 中:
function getGreeting(user){ if (user){ return <h1>Hello, {formatName(user)}!</h1>; } return <h1>Hello, Stranger.</h1>; }
咱们能够正常使用引号给 HTML 标签添加属性,也可使用 js 表达式
const element = <div tabIndex="0"></div>; const element = <img src={user.avatarUrl} />; //注意空标签以 /> 结尾,像 XML 同样
注意 html 属性名请使用小驼峰(camelCase)写法
React 会在渲染以前 escape 全部在 JSX 嵌入的值,能够有效的防止 XSS 攻击。
babel 会编译 JSX 成 React.createElement() 的参数调用:
const element = ( <h1 className="greeting"> Hello, world! </h1> ); // 编译为如下形式 const element = React.createElement( 'h1', {className: 'greeting'}, 'Hello, world!' );
而 React.createElement() 会生成这样一个对象(React 元素):
const element = { type: 'h1', props: { className: 'greeting', children: 'Hello, world' } };
在 ./public/index.html
中有一个 id 为 root 的 div。咱们将这个 div 做为 react 渲染的容器。
回看 hello world 程序,经过 ReactDOM.render() 方法很轻松的把内容渲染到了目标容器上:
ReactDOM.render( <h1> hello world! </h1>, document.getElementById('root') );
固然也能够这样写:
let content = <h1> hello world! </h1>; ReactDOM.render( content, document.getElementById('root') );
下面咱们写一个复杂的,这是个实时更新的时钟,经过 setInerval 每隔 1s 调用 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);
重写上面时钟组件的代码以下,使其组件化程度更高:
function Clock(props){ return ( <div> <h1>Hello, world!</h1> <h2>It is {props.date.toLocaleTimeString()}.</h2> </div> ); } function Tick(){ ReactDOM.render( //这个地方不得不传入一个参数, 但理论上获取一个时钟直接获取就能够了,这个问题咱们后面再解决 <Clock date={new Date()} />, document.getElementById('root') ); } setInterval(Tick, 1000);
React 给咱们提供了更好的管理个人代码——组件。这里咱们仍是首先咱们先了解一下自定义标签:
const element = <Welcome name="Sara" />;
对这个标签的理解也不难,它实际上调用了 Welcome 函数,而且将全部的属性(这里只有name)打包为一个对象传给 Welcome 函数。因此下面这个代码输出 ”Hello Sara"
function Welcome(props){ return <h1>Hello, {props.name}</h1>; } const element = <Welcome name="Sara" />; ReactDOM.render( element, document.getElementById('root') );
组件帮助我事先一些重复的工做,好比这样:
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') );
咱们能够经过传递参数获得同一个组件构建的不一样模块。
这里咱们须要补充一个重要的概念:__纯函数!!!__
若是一个函数执行过程当中不改变其参数,也不改变其外部做用于参数,当相同的输入总能获得相同的值时,咱们称之这样的函数为纯函数。__React 要求全部组件函数都必须是纯函数。__
其实以前的一段代码中 Tick, Welcome 函数就能够看作是一个组件,同时 React 建议组件名的首字母大写。可是更多状况下咱们会用到 es6 的语法构建组件。以以前时钟代码为例,转换过程分为五个步:
结果以下:
class Clock extends React.Component { render(){ return ( <div> <h1>Hello, world!</h1> <h2>It is {this.props.date.toLocaleTimeString()}.</h2> </div> ); } }
但这样计时的功能就不能用了,咱们继续往下看……
解决上面这个问题,就须要用到 State 和 Lifecycle 的知识了
咱们给 Clock 类添加一个构造函数,而且删除 Clock 标签中的参数:
class Clock extends React.Component { constructor(props){ super(props); this.state = {date: new Date()}; //state 用来记录状态 } render(){ return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } } ReactDOM.render( <Clock />, //删除参数 document.getElementById('root') );
为了控制计时的生命周期,咱们须要引入 2 个方法 componentDidMount() 和 componentWillUnmount(),前者在渲染(render方法)完成时当即执行,后者在该 render 的内容即将被移除前执行。
很明显,前者适合注册计时器,后者能够用来清除计时器(防止内存泄露)
componentDidMount(){ this.timerID = setInterval( () => this.tick(), 1000 ); } componentWillUnmount(){ clearInterval(this.timerID); }
下一步咱们重写 tick 函数,此时的 tick 函数只须要修改 this.state 就好了。注意 React 要求不能直接修改该属性,而是使用 setState() 方法,因此 tick 函数以下:
tick(){ this.setState({ date: new Date() }); }
这里须要注意的是,当 state 中有不少属性的时候,好比:
this.state = {name:"Lily", age: 12};执行 setState 方法修改其中的内容时并不会影响未修改的属性:
this.setState({name: "Bob"}); //此后 this.state 为 {name:"Bob", age: 12};此外 setState 多是异步的,因此不要在更新状态时依赖前值:
// 这是个反例 this.setState({ counter: this.state.counter + this.props.increment, });为例解决这个问题,你能够传入函数参数:
// Correct
this.setState((prevState, props) => ({ //这里 prevState 更新前的 state 对象,props 为新值构成的对象
counter: prevState.counter + props.increment
}));
此时,完整的代码为:
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>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } } ReactDOM.render( <Clock />, document.getElementById('root') );
React 事件注册和原生 DOM 事件相似的,这里须要理解一些不一样点便可:
<button onClick={activateLasers}> Click Here </button>
return false
的方式阻止默认事件,必须显式的调用 preventDefault(),而且在使用时不用纠结浏览器兼容问题,React 已经帮你处理好了render(){ return ( <button onClick={this.handleClick}> Click Here... </button> ); }
class Button extends React.Component { constructor(){ super(); this.name = "Bob"; this.click3 = this.click2.bind(this); this.click1 = () => { console.log(`hello ${this.name}`); } } click2(){ console.log(`hello ${this.name}`); } render(){ return ( <raw> <button onClick={this.click1}>Click1</button> <button onClick={this.click2}>Click2</button> <button onClick={this.click3}>Click3</button> <button onClick={(e) => this.click2(e)}>Click3</button> </raw> ); } }
class Button extends React.Component { constructor(){ super(); this.name = "Bob"; this.click = this.click.bind(this); } click(){ console.log(`hello ${this.name}`); } render(){ return ( <raw> <button onClick={this.click}>Click me</button> </raw> ); } }
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button> <button onClick={this.deleteRow.bind(this, id, e)}>Delete Row</button>
根据不一样的条件(一般指state)渲染不一样的内容, 好比下面段代码能够根据 isLoggenIn
渲染不一样的问候语:
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) { // 根据 isLoggenIn 渲染不一样的问候语 return <UserGreeting />; } return <GuestGreeting />; } ReactDOM.render( // 你能够尝试设置 isLoggedIn={true}: <Greeting isLoggedIn={false} />, document.getElementById('root') );
下面用 class 实现一个复杂一点的,带有登陆/注销按钮的:
function LoginButton(props) { return ( <button onClick={props.onClick}> 登陆 </button> ); } function LogoutButton(props) { return ( <button onClick={props.onClick}> 注销 </button> ); } class LoginControl extends React.Component { constructor(props) { super(props); this.state = { isLoggedIn: false }; // 修正 this 绑定 this.handleLoginClick = this.handleLoginClick.bind(this); this.handleLogoutClick = this.handleLogoutClick.bind(this); } handleLoginClick() { this.setState({isLoggedIn: true}); } handleLogoutClick() { this.setState({isLoggedIn: false}); } render() { const { isLoggedIn } = this.state; let button = null; if (isLoggedIn) { button = <LogoutButton onClick={this.handleLogoutClick} />; } else { button = <LoginButton onClick={this.handleLoginClick} />; } return ( <div> {/* Greeting 取自上一个示例 (注意这里的注释写法)*/} <Greeting isLoggedIn={isLoggedIn} /> {button} </div> ); } } ReactDOM.render( <LoginControl />, document.getElementById('root') );
固然,对于这样一个简单的示例,使用 if 可能你会觉的太复杂了,咱们也可使用 &&
?:
这些运算符来代替 if 语句,就像写 javascript 代码同样。咱们极力的化简一下上面的代码:
class LoginControl extends React.Component { constructor(props) { super(props); this.state = { isLoggedIn: false }; } render() { const { isLoggedIn } = this.state; const button = isLoggedIn ? <button onClick={() => { this.setState({isLoggedIn: false}); }}>注销</button> : <button onClick={() => { this.setState({isLoggedIn: true}); }}>登陆</button>; return ( <div> <h1> { isLoggedIn ? 'Welcome back!' : 'Please sign up.' } </h1> {button} </div> ); } } ReactDOM.render( <LoginControl />, document.getElementById('root') );
固然,若是你须要在某个条件下不进行渲染,那么直接输出 null 便可,好比下面这个组件,在 props.warn
为 false
时不渲染任何内容:
function WarningBanner(props) { if (!props.warn) { return null; } return ( <div className="warning"> Warning! </div> ); }
须要注意的是,即使你输出了 null, react 也会再渲染一次。同理,componentWillUpdate
和 componentDidUpdate
也会被调用。
在 React 中咱们可使用 map() 方法渲染列表,好比以下这个例子,将一组数据映射(map)为一组 dom:
const data = [1, 2, 3, 4, 5]; const listItems = data.map((item) => <li key={number.toString()}>{item}</li> ); ReactDOM.render( <ul>{listItems}</ul>, document.getElementById('root') );
咱们注意到这里咱们给 li (即列表的每一个元素)标签加了一个 key 属性,这个 key 用来帮助 React 判断哪一个元素发生了改变、添加或移除。关于这个 key 咱们须要明白如下几点:
固然,上面代码咱们也能够写成 inline 的形式:
const data = [1, 2, 3, 4, 5]; ReactDOM.render( <ul> { data.map((item) => <li key={number.toString()}>{item}</li> ); } </ul>, document.getElementById('root') );
表单的处理会和原生的 html 有一些区别,由于 React 能够很好的帮助你使用 js 控制你的表单,这里咱们须要引入一个新的概念:受控组件。
受控组件说白了就是其值受 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('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> ); } }
不难发现,这里使用了,onchange 事件不断的将用户的输入绑定到 this.state.value 上,而后经过和用户输入同步的重绘实现数据的显示。这样能够很好的控制用户输入,好比同步的将用户输入转化为大写:
handleChange(event) { this.setState({value: event.target.value.toUpperCase()}); }
理解了上面的内容咱们能够知道,单纯给一个 input 赋值一个值用户是不能修改的,好比下面这行代码:
ReactDOM.render(<input value="hi" />, mountNode);
但若是你不当心他的值设为 null 或 undefined(等同于没有 value 属性),这个 input 就能够被更改了:
ReactDOM.render(<input value="hi" />, mountNode); setTimeout(function() { ReactDOM.render(<input value={null} />, mountNode); }, 1000);
在 React 中 textarea 也是经过 value 属性实现其内容变化的,而非其子节点:
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> Essay: <textarea value={this.state.value} onChange={this.handleChange} /> </label> <input type="submit" value="Submit" /> </form> ); } }
在 React 中,对于 select 也会显得很方便,你不须要在 option 中经过 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> ); } }
上面代码默认选中 Coconut。 这里值得注意的是,对于多选框,你能够传入一个数组做为值:
<select multiple={true} value={['B', 'C']}>
当你控制不少个表单组件的时候要是为每一个组件写一个 handler 方法做为 onChange 事件那就太麻烦了。因此 React 能够经过表单元素的 name 配合 event.target.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; const value = target.type === 'checkbox' ? target.checked : target.value; const { name } = target; 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"s value={this.state.numberOfGuests} onChange={this.handleInputChange} /> </label> </form> ); } }
这个部分,想说的不是个语法问题,而是代码结构问题。咱们重点理解一个例子:计算温度的功能。
咱们实现2个输入框(摄氏温度和华氏温度)的同步数据显示,和对数据的简单操做(判断是否达到标况下水的沸点100摄氏度)
咱们先作点准备工做,好比温度转换函数:
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) || typeof convert !== 'function') { return ''; } const output = convert(input); const rounded = Math.round(output * 1000) / 1000; return String(rounded); }
咱们先简单实现这个功能:
function 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.handleChange = this.handleChange.bind(this); this.state = {temperature: ''}; } handleChange(e) { this.setState({temperature: e.target.value}); } render() { const { temperature } = this.state; return ( <fieldset> <legend>Enter temperature in Celsius:</legend> <input value={temperature} onChange={this.handleChange} /> <BoilingVerdict celsius={parseFloat(temperature)} /> </fieldset> ); } }
此时咱们能够输入摄氏温度了,再添加一个数据华氏温度的地方。这里咱们从上面的 Calculator 中提出来输入组件:
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; const { scale } = this.props; return ( <fieldset> <legend>Enter temperature in {scaleNames[scale]}:</legend> <input value={temperature} onChange={this.handleChange} /> </fieldset> ); } }
这样 Calculator 就简单了:
class Calculator extends React.Component { render() { return ( <div> <TemperatureInput scale="c" /> <TemperatureInput scale="f" /> </div> ); } }
这样2个输入框就有了,可是它们还不能同步变化。并且 Calculator 组件不知道水温是多少了,无法判断温度了。这是咱们应该吧温度状态放在他们最近的公共祖先元素上,这里就是 Calculator 组件啦。
很明显,首先要改的就是 TemperatureInput, 它不须要 state 了,咱们应该从参数获取温度了:
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, scale } = this.props; return ( <fieldset> <legend>Enter temperature in {scaleNames[scale]}:</legend> <input value={temperature} onChange={this.handleChange} /> </fieldset> ); } }
以后咱们修改 Calculator 的 state, 将 temperature 和 scale 放入其中, 并添加状态转换函数:
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({scale: 'c', temperature}); } handleFahrenheitChange(temperature) { this.setState({scale: 'f', temperature}); } render() { const { temperature, scale } = this.state; 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> ); } }
到此全部的工做就完成了。咱们总结一下,输入数据时都发生了什么
咱们看看官方给出的效果:
React 建议用组件组合的方式代替组件继承。因此咱们须要学习如何用组合代替继承。
不少组件在事先是不知道本身的孩子(内部的元素)的。好比对话框这样的盒子型元素。咱们须要使用 children 属性来解决这个问题
function FancyBorder(props) { return ( <div className={'FancyBorder FancyBorder-' + props.color}> {props.children} </div> ); }
props.children 表示经过其余组件调用 FancyBorder 时的所有孩子元素,对应下面例子,children 就是 h1 和 p 的 react 对象数组
function WelcomeDialog() { return ( <FancyBorder color="blue"> <h1 className="Dialog-title"> Welcome </h1> <p className="Dialog-message"> Thank you for visiting our spacecraft! </p> </FancyBorder> ); }
可是当组件缺少共同点的时候,咱们须要在组件中开不少孔,就像下面这个例子,这些孔能够很好的帮咱们组合使用不少组件,并且 react 并不限制我咱们传递参数的类型
function SplitPane(props) { return ( <div className="SplitPane"> <div className="SplitPane-left"> {props.left} </div> <div className="SplitPane-right"> {props.right} </div> </div> ); } function App() { return ( <SplitPane left={ <Contacts /> } right={ <Chat /> } /> ); }
有时候咱们想对组件作具体化分类的时候,逻辑上会很像继承,好比 WelcomeDialog 是 Dialog 的一个具体化分类。但在 React 中咱们依然用组合的方式实现这个功能:
function Dialog(props) { return ( <FancyBorder color="blue"> <h1 className="Dialog-title"> {props.title} </h1> <p className="Dialog-message"> {props.message} </p> </FancyBorder> ); } function WelcomeDialog() { return ( <Dialog title="Welcome" message="Thank you for visiting our spacecraft!" /> ); }
固然咱们也能够用 class 的定义方式:
function Dialog(props) { return ( <FancyBorder color="blue"> <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.handleChange = this.handleChange.bind(this); this.handleSignUp = this.handleSignUp.bind(this); this.state = {login: ''}; } render() { return ( <Dialog title="Mars Exploration Program" message="How should we refer to you?"> <input value={this.state.login} onChange={this.handleChange} /> <button onClick={this.handleSignUp}> Sign Me Up! </button> </Dialog> ); } handleChange(e) { this.setState({login: e.target.value}); } handleSignUp() { alert(`Welcome aboard, ${this.state.login}!`); } }