摘要:中的方法调用了并传入了最新的值。在前一次渲染中,已经将的设置为,并将的设置为
本文采用 es6 语法,完全参考 https://reactjs.org/docs/安装
本文完全参考 React 官方 Quick Start 部分,除了最后的 thinking-in-react 小节
首先你需要点击安装 nodejs(npm)。然后执行:
npm install -g create-react-app
如果上述命令执行失败可以运行以下命令:
npm install -g create-react-app --registry=https://registry.npm.taobao.org
然后建立一个 react 并运行:
create-react-app myApp cd myApp npm start
这样你就简单的完成了一个 react app 建立,其目录结构如下( 图中不包括 node_modules 目录,下同 ):
Hello World我们删除一些不必要的东西,然后修改目录结构如下(不能删 node_modules 目录,如果删了就在项目目录下运行 npm i 就好了):
其中 components 是个目录。
修改 index.js 如下:
import React from "react"; import ReactDOM from "react-dom"; ReactDOM.render(hello world!
, document.getElementById("root") );
然后命令行运行:
npm start
你就可以看到熟悉的 "hello world" 了
JSXJSX 是 react 中允许 js 和 html 混写的语法格式,需要依赖 babel 编译。这里我就只研究它的语法:
const element =Hello, world!
;
可以通过花括号在其中插入表达式:
function formatName(user){ return user.firstName + " " + user.lastName; } const user = { firstName: "Harper", lastName: "Perez" }; const element = (Hello, {formatName(user)}!
); ReactDOM.render( element, document.getElementById("root") );
可以将 HTML 语句写为多行以增加可读性,用小括号括起来可以防止自动插入分号导致的错误。
JSX 也是个表达式,所以可以用在 for 和 if 中:
function getGreeting(user){ if (user){ returnHello, {formatName(user)}!
; } returnHello, Stranger.
; }
我们可以正常使用引号给 HTML 标签添加属性,也可以使用 js 表达式
const element = ; const element = ; //注意空标签以 /> 结尾,像 XML 一样
注意 html 属性名请使用小驼峰(camelCase)写法
React 会在渲染之前 escape 所有在 JSX 嵌入的值,可以有效的防止 XSS 攻击。
babel 会编译 JSX 成 React.createElement() 的参数调用:
const element = (Hello, world!
); // 编译为以下形式 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(hello world!
, document.getElementById("root") );
当然也可以这样写:
let content =hello world!
; ReactDOM.render( content, document.getElementById("root") );
下面我们写一个复杂的,这是个实时更新的时钟,通过 setInerval 每隔 1s 调用 ReactDOM.render:
function Tick(){ const element = (); ReactDOM.render( element, document.getElementById("root") ); } setInterval(Tick, 1000);Hello, world!
It is {new Date().toLocaleTimeString()}.
重写上面时钟组件的代码如下,使其组件化程度更高:
function Clock(props){ return (组件); } function Tick(){ ReactDOM.render( //这个地方不得不传入一个参数, 但理论上获取一个时钟直接获取就可以了,这个问题我们后面再解决Hello, world!
It is {props.date.toLocaleTimeString()}.
, document.getElementById("root") ); } setInterval(Tick, 1000);
React 给我们提供了更好的管理我的代码——组件。这里我们还是首先我们先了解一下自定义标签:
const element =;
对这个标签的理解也不难,它实际上调用了 Welcome 函数,并且将所有的属性(这里只有name)打包为一个对象传给 Welcome 函数。所以下面这个代码输出 ”Hello Sara"
function Welcome(props){ returnHello, {props.name}
; } const element =; ReactDOM.render( element, document.getElementById("root") );
组件帮助我事先一些重复的工作,比如这样:
function Welcome(props){ returnHello, {props.name}
; } function App(){ return (); } ReactDOM.render(, document.getElementById("root") );
我们可以通过传递参数得到同一个组件构建的不同模块。
这里我们需要补充一个重要的概念:__纯函数!!!__
如果一个函数执行过程中不改变其参数,也不改变其外部作用于参数,当相同的输入总能得到相同的值时,我们称之这样的函数为纯函数。__React 要求所有组件函数都必须是纯函数。__
其实之前的一段代码中 Tick, Welcome 函数就可以看做是一个组件,同时 React 建议组件名的首字母大写。但是更多情况下我们会用到 es6 的语法构建组件。以之前时钟代码为例,转换过程分为五个步:
新建一个类,类名同组件函数名Clock,并继承自 React.Component;
给该类添加一个方法 render(/无参数/);
将 Clock 的函数体作为该函数的函数体;
将 render 方法中的 props 换为 this.props;
删除原有的 Clock 函数
结果如下:
class Clock extends React.Component { render(){ return (); } }Hello, world!
It is {this.props.date.toLocaleTimeString()}.
但这样计时的功能就不能用了,我们继续往下看……
State 和 Lifecycle解决上面这个问题,就需要用到 State 和 Lifecycle 的知识了
我们给 Clock 类添加一个构造函数,并且删除 Clock 标签中的参数:
class Clock extends React.Component { constructor(props){ super(props); this.state = {date: new Date()}; //state 用来记录状态 } render(){ return (); } } ReactDOM.render(Hello, world!
It is {this.state.date.toLocaleTimeString()}.
, //删除参数 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 (事件); } } ReactDOM.render(Hello, world!
It is {this.state.date.toLocaleTimeString()}.
, document.getElementById("root") );
React 事件注册和原生 DOM 事件类似的,这里需要理解一些不同点即可:
事件名使用小驼峰写法,而不是全小写,例如:onclick 写作 onClick
注册事件使用花括号表达式替代原有函数写法
无法通过事件函数 return false 的方式阻止默认事件,必须显式的调用 preventDefault(),并且在使用时不用纠结浏览器兼容问题,React 已经帮你处理好了
React 建议通常不需要通过 addEventListener 添加事件,只需要像上方代码那样在 render 时绑定事件即可
在 es6 语法的组件中注册事件只需要将事件函数定义为该类的一个方法,然后在 render 时绑定即可:
render(){ return ( ); }
在 class 中,除了箭头函数定义的方法中 this 符合预期,其余方法中的 this 都是 undefined,应该手动绑定。因此以下三个按钮中 click2 会报错。
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 (); } }
以上几种方法,React 推荐使用 click3 的实现方法,重写如下:
class Button extends React.Component { constructor(){ super(); this.name = "Bob"; this.click = this.click.bind(this); } click(){ console.log(`hello ${this.name}`); } render(){ return (); } }
传递参数给事件的时候,第一个参数为 id, 第二个参数为 event。实际调用可以去以下两种方式:
以上两种方法等效,后一个方法的参数 e 可以省略。
条件渲染根据不同的条件(通常指state)渲染不同的内容, 比如下面段代码可以根据 isLoggenIn 渲染不同的问候语:
function UserGreeting(props) { returnWelcome back!
; } function GuestGreeting(props) { returnPlease sign up.
; } function Greeting(props) { const isLoggedIn = props.isLoggedIn; if (isLoggedIn) { // 根据 isLoggenIn 渲染不同的问候语 return; } return ; } ReactDOM.render( // 你可以尝试设置 isLoggedIn={true}: , document.getElementById("root") );
下面用 class 实现一个复杂一点的,带有登录/注销按钮的:
function LoginButton(props) { return ( ); } function LogoutButton(props) { return ( ); } 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 =; } else { button = ; } return ( {/* Greeting 取自上一个示例 (注意这里的注释写法)*/}); } } ReactDOM.render({button} , 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 ? : ; return (); } } ReactDOM.render({ isLoggedIn ? "Welcome back!" : "Please sign up." }
{button}, document.getElementById("root") );
当然,如果你需要在某个条件下不进行渲染,那么直接输出 null 即可,比如下面这个组件,在 props.warn 为 false 时不渲染任何内容:
function WarningBanner(props) { if (!props.warn) { return null; } return (Warning!); }
需要注意的是,即便你输出了 null, react 也会再渲染一次。同理,componentWillUpdate 和 componentDidUpdate 也会被调用。
列表在 React 中我们可以使用 map() 方法渲染列表,比如如下这个例子,将一组数据映射(map)为一组 dom:
const data = [1, 2, 3, 4, 5]; const listItems = data.map((item) =>
我们注意到这里我们给 li (即列表的每个元素)标签加了一个 key 属性,这个 key 用来帮助 React 判断哪个元素发生了改变、添加或移除。关于这个 key 我们需要明白以下几点:
最好保证 key 是一个字符串,并且在该列表中唯一,如果你的数据中实在没有唯一的 key 可以选择,那么就使用数组的索引(index)吧(不推荐这样)
值得注意的是,如果你不给每个元素指定一个 key, react 会默认使用索引(index)作为 key
key 的值只是给 React 起到类似暗示的作用,不会真正的传递给 dom, 所以如果你需要使用 key 的值,应使用一个其它变量传递该值。
当然,上面代码我们也可以写成 inline 的形式:
const data = [1, 2, 3, 4, 5]; ReactDOM.render(
表单的处理会和原生的 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 (); } }
不难发现,这里使用了,onchange 事件不断的将用户的输入绑定到 this.state.value 上,然后通过和用户输入同步的重绘实现数据的显示。这样可以很好的控制用户输入,比如同步的将用户输入转化为大写:
handleChange(event) { this.setState({value: event.target.value.toUpperCase()}); }
理解了上面的内容我们可以知道,单纯给一个 input 赋值一个值用户是不能修改的,比如下面这行代码:
ReactDOM.render(, mountNode);
但如果你不小心他的值设为 null 或 undefined(等同于没有 value 属性),这个 input 就可以被更改了:
ReactDOM.render(, mountNode); setTimeout(function() { ReactDOM.render(, 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 (); } }
在 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 (); } }
上面代码默认选中 Coconut。 这里值得注意的是,对于多选框,你可以传入一个数组作为值:
当你控制很多个表单组件的时候要是为每个组件写一个 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 (); } } state 提升
这个部分,想说的不是个语法问题,而是代码结构问题。我们重点理解一个例子:计算温度的功能。
我们实现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) { returnThe water would boil.
; } returnThe water would not boil.
; }
然后我们写一个组件用来让用户输入温度
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 (); } }
此时我们可以输入摄氏温度了,再添加一个数据华氏温度的地方。这里我们从上面的 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 (); } }
这样 Calculator 就简单了:
class Calculator extends React.Component { render() { return (); } }
这样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 (); } }
之后我们修改 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 (); } }
到此所有的工作就完成了。我们总结一下,输入数据时都发生了什么
当用户输入时,react 调用了已经申明的函数作为 onChange 事件。在这个例子中,就是 TemperatureInput 中的 handleChange 方法。
TemperatureInput 中的 handleChange 方法调用了 this.props.onTemperatureChange(), 并传入了最新的值。而这个方法由父组件 Calculator 提供。
在前一次渲染中,Calculator 已经将 Celsius TemperatureInput 的 onTemperatureChange 设置为 handleCelsiusChange,并将 Fahrenheit TemperatureInput 的 onTemperatureChange 设置为 handleFahrenheitChange。所以这两个计算方法的调用取决于我们编辑哪一个 input。
在这两个方法中,Calculator 组件通过调用 this.setState() 方法让 react 以最新的输入和当前 scale 重绘该组件。
React 调用 Calculator 组件的 render 方法渲染页面,两个 input 中的值会基于当前值和 scale 重新计算, 温度转换函数就是在这里被调用的。
React 通过 props 使用 Calculator 新传入的数据,分别调用每个 TemperatureInput 模块中的 render 方法渲染 input 组件。
React DOM 更新 DOM 树匹配新的数据,我们编辑的 input 得到我们刚刚输入的值,而另一个 input 得到转换后的值。
我们看看官方给出的效果:
组合与继承React 建议用组件组合的方式代替组件继承。所以我们需要学习如何用组合代替继承。
很多组件在事先是不知道自己的孩子(内部的元素)的。比如对话框这样的盒子型元素。我们需要使用 children 属性来解决这个问题
function FancyBorder(props) { return ({props.children}); }
props.children 表示通过其他组件调用 FancyBorder 时的全部孩子元素,对应下面例子,children 就是 h1 和 p 的 react 对象数组
function WelcomeDialog() { return (); } Welcome
Thank you for visiting our spacecraft!
但是当组件缺乏共同点的时候,我们需要在组件中开很多孔,就像下面这个例子,这些孔可以很好的帮我们组合使用很多组件,而且 react 并不限制我我们传递参数的类型
function SplitPane(props) { return (); } function App() { return ({props.left}{props.right}} right={ } /> ); }
有时候我们想对组件做具体化分类的时候,逻辑上会很像继承,比如 WelcomeDialog 是 Dialog 的一个具体化分类。但在 React 中我们依然用组合的方式实现这个功能:
function Dialog(props) { return (); } function WelcomeDialog() { return ( ); } {props.title}
{props.message}
当然我们也可以用 class 的定义方式:
function Dialog(props) { return (); } 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 ( ); } handleChange(e) { this.setState({login: e.target.value}); } handleSignUp() { alert(`Welcome aboard, ${this.state.login}!`); } } {props.title}
{props.message}
{props.children}
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/97630.html
摘要:考虑到是快速入门,于是乎我们就记住一点,当修改值需要重新渲染的时候,的机制是不会让他全部重新渲染的,它只会把你修改值所在的重新更新。这一生命周期返回的任何值将会作为参数被传递给。 安装react npm install creat-react-app -gshowImg(https://segmentfault.com/img/remote/1460000015639868); 这里直...
摘要:一份开发者必备的技能清单,请查收。入门查漏补缺深入学习查看原图下载源文件使用快速上手,并了解其中的概念。官方教程入门教程小书文章精读,问题解答。 一份react开发者必备的技能清单,请查收。入门、查漏补缺、深入学习... showImg(https://segmentfault.com/img/remote/1460000018000950?w=1965&h=3332); 查看原图 ...
摘要:本篇解释中类的控制指令,与指令式界面设计相关。本专栏历史文章介绍一项让可以与抗衡的技术可视化开发工具非正经入门之一三宗罪可视化开发工具非正经入门之二分离界面设计可视化开发工具非正经入门之三双源属性与数据驱动可视化开发工具非正经入门之四 本系列博文从 Shadow Widget 作者的视角,解释该框架的设计要点。本篇解释 Shadow Widget 中类 Vue 的控制指令,与指令式界面...
摘要:今年以来,的文档更新很快完善社区也日渐状大,再加上于某厂你懂的大力的推广,的前景十分光明。一般情况下,中小型的系统从迁移到版本大概只需要天的时间。快去动手尝试吧原创新书移动前端高效开发实战已在亚马逊京东当当开售。 作者:晓飞(沪江Web前端开发工程师)本文原创,转载请注明作者及出处 Vue.js框架已经火了好长一段时间了,早在2015年的双11中,淘宝的部分导购业务——如:双十一晚会摇...
摘要:一团队组织网站说明腾讯团队腾讯前端团队,代表作品,致力于前端技术的研究腾讯社交用户体验设计,简称,腾讯设计团队网站腾讯用户研究与体验设计部百度前端研发部出品淘宝前端团队用技术为体验提供无限可能凹凸实验室京东用户体验设计部出品奇舞团奇虎旗下前 一、团队组织 网站 说明 腾讯 AlloyTeam 团队 腾讯Web前端团队,代表作品WebQQ,致力于前端技术的研究 ISUX 腾...
阅读 2141·2021-09-04 16:40
阅读 1419·2021-08-13 15:07
阅读 3580·2019-08-30 15:53
阅读 3174·2019-08-30 13:11
阅读 1032·2019-08-29 17:22
阅读 1762·2019-08-29 12:47
阅读 1447·2019-08-29 11:27
阅读 2199·2019-08-26 18:42