字幕列表 影片播放 列印英文字幕 [PIANO PLAYING] SPEAKER: OK, let's get started. Welcome back, everyone, to CS50 Beyond. Our goal for today-- this is the second-to-last day of the course. And goal for today is going to be to pick up really where we left off yesterday. So yesterday we introduced a new JavaScript library called React. And React was designed to make it easier for us to build interactive and dynamic web pages. And we did so by creating components and ultimately via declarative programming, where we were describing what it is that the page should look like, whether certain variables should belong in certain places on the page, whether we should be iterating over a particular variable in order to display a list of things on the page. And then the only thing we needed to do in order to edit the page was rather than go into the dom and manipulate the dom and say, update this element or update that element, we would just say, change the data. Set the state of the component to be something different. And the page would automatically render and re-update the parts of the page that needed to in order to reflect those changes. And so the goal of today is to continue along those lines and in particular, emphasize thinking in React, thinking about how it is that we take an application that we're going for, decompose it into pieces, and then try and put it back together, such that we can figure out what components, what state we need to keep track of in order to make our applications interactive and useful. Along the way we'll talk about things like React lifecycle and about how to get your React applications, which are front-end applications, to interact with the back end, like a server, for instance, connecting to APIs then. And then we'll have some opportunity for hands-on projects, as before. And so we'll start by taking a look at an example from yesterday and begin to answer some of the questions that yesterday I pushed off until today. And so we'll take a look at counter.html And counter.html, you'll recall from yesterday, was just an application whereby it displayed a number that defaulted to the number 0. And then we had increment and decrement buttons, or plus and minus buttons, where all those buttons' data will change the value of the number. We started with 0. We can go 1, 2, 3, 4, 5 by clicking the Increment button plus the Minus button in order to go back down. And ultimately the way that we implemented that program was to say inside of counter.html, we had some state inside of our counter component that initially was just set to the number 0. And then when we click the On-click button for the plus or minus buttons, we would call the increment or decrement functions. And all those increment and decrement functions would do is call the this.setState method, which is a special method in React that takes the state and updates it, in particular by taking the count and setting it to whatever the count minus 1 is in the case of decrement, and in the case of increment, taking the count and adding 1 to it. Of course, this state only exists when this component is loaded onto the screen. And as soon as we close the page and reopen it, we're getting a brand-fresh, new component. It's remount onto the screen. And as a result, if I increase the number to, say, 5, for example, close the page, and then reopen the counter.html, we're back to 0, right? We reloaded the page. The JavaScript is rerun. And as a result, our state is reset back to the initial state as defined by the constructor, which, in this case, is the number 0. Now, before we got to the world of React and we were just dealing with plain JavaScript, how do we solve this problem, if we wanted the state to persist between when you open the page, closed the page, open it again? We cached it in a place called local storage, a place within the browser, whereby we could say I want to store the state of the application inside of local storage, such that later on in my application I can refer back to local storage and say, get this value out of local storage and put it into my application. And in React, we can do something very similar. Every React component has a number of what are called lifecycle methods, or functions that are going to run at a particular point in the component's life. And one of the most common is a special function in React called componentDidMount. There are a number of others. The only one we're going to talk about today is componentDidMount And componentDidMount is a special method in React that is going to run as soon as a component is mounted into the dom. In other words, as soon as we add this component into the dom, this is the method that's going to run before it actually ultimately renders the contents of what's going to show up. So any work we want to do of, like, setting up the component, we can do inside of componentDidMount. And so one thing we can do, for example, is get something out of local storage. I can say, local storage.getItem count in order to say, all right, let me get the count value out of local storage. And I'll save that inside of a variable that I'm just going to call count. So if there was an item called count in local storage, I'm going to extract it from local storage and save it inside of a variable called count. Now, there's a chance that there was no count item in local storage, that this is not equal to any particular value. And so it's possible that count is going to be equal to null. So let me just do a check. Let me check if count is not equal to null, meaning there actually was some count saved inside of local storage, well, then what I'm going to do is I'm going to call this.setState, setting the count equal to the result of parse int count. It's going to be stored as a string so we'll parse it as an integer in order to update the state of the application. So componentDidMount, if you think about the chronology of things, is going to happen after we've constructed this component and inserted it into the dom. And we want some initial code to be running at the beginning of this component's life, whereby we're saying from local storage, let's extract the counter variable. And as long as it's not null, then go ahead and update the state, setting count equal to the result of parsing the int of whatever the value of the count variable is. So now we have a component that can read from local storage and update its initial state based on that information. What's the missing half of this now? Yeah? AUDIENCE: [INAUDIBLE] isn't count equal to count? Why [INAUDIBLE]? SPEAKER: So the way local storage is going to store things is it's going to store the values of strings. And so that's the way that most browsers choose to implement local storage. And so when we read them from local storage, we have to assume that what's coming into us will be strings. And so we can parse them into integers in order to be able to use them. So we've got saving things-- or retrieving things from local storage. And now that I just gave away the missing piece of this is saving things into local storage, whereby we're getting the item count, but we're never setting the item count. And so we want this to happen before the window goes away. If ever I try to close the window, we want something to happen there. And so we'd like to add some sort of event listener for when I try to close the window, for example. And oftentimes you might see other web applications that have implemented something similar, whereby if you try to exit out of a web document before saving, you might get a warning message that says, are you sure you want to leave this page without saving your changes, for example. Or you haven't completed this action. Are you sure you want to do this thing? And so just in the same way that we were able to add event listeners to when the dom is done loading or when you click on a button, we can also add an event listener to the window to say, like, before this window's contents are unloaded, let's run some particular code. And so to do that, I'll say window.addEventListener. And the name of this event that we're going to be using is a special event called before unload. In other words, before this window's contents are unloaded, let's run some code. Before we get rid of this component, let's run a function. And that function is going to say local storage.setItem. And then setItem, again, takes two arguments. The first is the key, the name of the thing that we're setting. And the second is the value, what value it should take on. The key, as with line 22, we got the item with key count. So we should be setting the item with key count. And what is the value? What should we be setting count equal to? Yeah? AUDIENCE: This.state.count? SPEAKER: This.state.count, perfect. We want to set count equal to whatever the current value of count is inside of this component state. To get to this component state, we can say this.state to get at the state of the component and then dot count it to say, all right, let's go into the state, extract the thing with the key count, and use that as the value that we save into local storage. And so assuming I didn't make any mistakes here, if I go back to the page, refresh the page, I can increment the counter 1, 2, 3, 4, 5. And now if I close the counter, that's going to trigger the window.beforeUnload event, which is going to save the number 5 into local storage. And if I open counter.html again, press Return, all right, great, the component now shows me the number 5. It's been able to save that state inside of local storage, such that even when I closed the page and open it back up again, I've been able to maintain the state of the application. Yeah? AUDIENCE: [INAUDIBLE] SPEAKER: For refreshing the page as well? Yeah, if you refresh the page, if I go to 10, for example, and click Refresh, it stays at 10 because before the contents of the page are unloaded before the refresh, it's going to trigger that event listener. Yeah? AUDIENCE: Are the items that are kept in local storage-- like say you want [INAUDIBLE] the program [INAUDIBLE].. Are the items in local storage, are they secure from-- if someone hacked into your code, broke in, could they get into it? SPEAKER: The items inside of local storage are actually quite accessible to anyone. So anyone who could get access to the browser could get access to it, insofar as all I need to do is go into inspect, and I can go into, I think, it's application, at least in Chrome, and go to local storage. And over here I see a count and also a counter from a previous time that I was doing an application with local storage. So these values are highly accessible. And you can edit them as well. And so probably best not to store anything super secure inside of local storage for that reason. You'll probably want to store things on the server side. And we'll talk about interaction with server side in just a moment. Yeah? AUDIENCE: Will you let me know the website [INAUDIBLE] variables that you put in local storage [INAUDIBLE]?? SPEAKER: Local storage is domain specific. So if you have domain1.com, and you're storing things inside of local storage, domain2.com is going to have access to a different local storage. And so those are kept separate. AUDIENCE: Is there a way to make it so local pages use the same [INAUDIBLE]?? SPEAKER: So, yeah, if they're within the same domain, they can use the same local storage. For example, if I were to create another file-- and I'll just demonstrate. Like, if I just copy counter.html and call it, like, newcounter.html, which is a different file, and I open up newcounter.html, it's still going to have access to that same local storage. It still has access to the number 10. So it can draw up on those same values. Other things? Yeah? AUDIENCE: Can you just explain a little bit more of the difference between componentDidMount and why do we need didMount [INAUDIBLE]?? SPEAKER: Yeah. So componentDidMount, there are a number of what are called lifecycle methods in React, which are canonical places where you can put code, and you can guarantee that React will call upon those functions at particular times. And so in fact, so componentDidMount is one of them. ComponentWillUnmount is another one of them that React offers, which is sort of the inverse of that, which is when we're about to remove information from the dom. There's even a componentWillUpdate for whenever a component is going to refresh itself and change something about its behavior. And so there are a lot of these different lifecycle methods and more than we'll probably have time to really be able to talk about over the course of today. But I'd encourage you to look at them. There are great flow charts online of what it looks like and when different methods are called. And each one is called at a slightly different time. And so getting an understanding for when each of them are called can help you to really fine tune a component to make sure that it behaves in particular ways and only running certain code when it needs to run. So that's local storage. So a couple of people were asking about how do you save React component states, such that it's available the next time you load the program. And that's certainly one way to do it. And so far with regards to React, all of our programs have been just front-end applications, right? They just exist in the browser. There's no interaction with a back-end server, with a database, like we were doing when we were working with Flask and SQL, for example. And so there are certainly ways that we can do that. And the example that we'll use is we'll take an example that we did back when we were working and just plain JavaScript, which is that of currency conversion. So you probably recall that we had api.exchangeratesapi.io. And if I go to a particular URL, like slash latest, and then specify the base being US dollars, then I get all sorts of these different exchange rates in terms of US dollars, that one US dollar is equal to 1.3312 Canadian dollars, for example, and so on and so forth. And so what I'd like to do is create a React application that is going to be able to use these currency exchange rates. And it's going to be a fair bit more interactive than the previous one that we created was, in particular, because React is going to make it easier for us to dynamically update the page. So if I go in to exchange0.html, an application we might create is going to look a little something like this, whereby I have two drop-downs, each of which I can select a currency. This one's saying US dollars. This one's saying euros, at least for now. And I can say, all right, one US dollar is equal to that many euros. I can say, all right, 15 US dollars is equal to that many euros. And it's going to dynamically calculate for me what the exchange rates are. If I change euros to pounds, for example, it tells me the exchange rates between US dollars and pounds. And so we can get these exchange rates, much as any currency converter app that you may have used before might work, by just selecting the currencies we want, typing in into the input field what it is is the amount that we want to convert, and then see in this other field what that converted amount is equal to. And so this is the application that we're going to be building. I'll go ahead and create a new file. We'll call it exchange.html. And for now I'm going to copy the contents of counter, paste it in here, and we'll get rid of the counter component because I don't need it. I'll change the title to Exchange. And what do we need inside of our application? Any thoughts as to what type of information needs to be in the state of this application? What things can change in the application that we had before? AUDIENCE: [INAUDIBLE] SPEAKER: Yeah, the base currency that I select. So we need some sort of base currency that we're storing in the state. By default, my original application just used US dollars as the initial value of the base currency. And we also needed some way of storing the other currency, whatever else that I typed in. So go ahead and type in other. And then I think by default my application said let's make euros the other currency by default. But you can switch them up. What else could change? Yeah? AUDIENCE: The amount of base currency? SPEAKER: The amount of the base currency, yeah, so some sort of volume that initially was 0. And then there was also the converted value that was also 0 to begin with. And so I'll go ahead and create another key inside the state called converted and set that equal to 0 as well. So we have this.state. And in addition to having these currencies, the base currency and the other currency, the dropdown list also had a list of all the possible currency. That's a list that I might want to have programmatically inside my code so that it's easy to update if I ever want to add more. So for sake of example, I'm just going to add this.currencies, a new variable, just called currencies, stored inside of this component that's just going to be equal to a list of possible currencies that I want to allow people to exchange between, so maybe Australian dollars and Canadian dollars, each one just using their three letter ISO code for what the abbreviation for that currency happens to be. We'll do francs. We'll allow those. Chinese yuan we'll allow. Indian rupees we'll allow. We should do US dollars and euros. We'll do pounds, yen. And we'll also do New Zealand dollars. So there are more currencies than just this. But we'll just do a small sampling of currencies for the sake of example. Semicolon there, and, OK, we've defined the initial state of this component. And now let's render the component. What should it actually look like when we try and render it? Well, if you remember what the page looked like, there was a select menu and an input field up top for the base currency and then a select menu and an input field underneath it for the other currency. So I'll go ahead and create two divs inside of my outer div. The first of which is going to be for the base currency. And the second of which is going to be for the other currency. So the base currency is going to have a select menu. And the select menu is going to have a name of base. And for now I'll just say select name as base and slash select to end it that we'll add to this in just a moment. And in the other div, I'll have a select menu, whose name is Other. And that'll also be a select menu. So I now have a base select menu on top of another select menu. And the value of this select menu, like which item is chosen in the select menu, is going to be represented by which part of the state, base, other value, or converted? OK, someone said it, base. So this.state.base, that's going to be with a value that this select menu takes on because I want to bind to the select menu effectively to part of the state. And the part of the state that I want to connect it to is the base. Whatever base currency, that's the option that is selected in this drop-down menu. And likewise for the other select menu, as you might imagine, the value of that select menu is going to be this.state.other. Now, inside of each select menu I want options. I want a option, a tag, for each of the possible currencies. And so to do that, I'm just going to go ahead and map over all of the currencies. So I can say this.currencies.map and each currency. So I have an array of currencies. I'm mapping over each one, one at a time, taking each individual currency and turning it into an option. That option's going to show up with a text. Text of the option should just be the name of the currency. And the option needs to have a key because React requires anything that you iterate over to have some sort of key to uniquely identify it. The key you can just be the name of the currency. And the option also needs to have some value. And the value, in this case, is also just going to be the name of the currency. Because when I select an option, its value is going to be the new currency that I want to use. I can go ahead and take this code and use it again inside of the other select menu. You can imagine factoring this out into a variable and then inserting it to avoid redundancy. And so now let me go ahead and open up exchange.html. Now what we see is I have two drop-downs, one that defaults to US dollars but that shows me all the possible currencies I can choose from, and one that defaults to euros that also shows me all of the currencies that I can choose from as well. So all right, we're making progress. But of course, if I try and choose something else, if I try and choose Canadian dollars, for example, from this select menu, it stays as US dollars. It doesn't change because my state is never changing. My state is always base is US dollars and other is euros. So I need to have some code that says when I change the select menu, let's go ahead and actually make that selection so to the select menu I'll add in onChange handler. So when the select option changes, we'll go ahead and call a function. And I'll call it this.makeSelection. But you could certainly call it whatever you want. Likewise, when I click on another currency, I'll also say, when that changes, let's call this.makeSelection. And now it's going to be a function I want to run. I'll go ahead and add a makeSelection function. It takes the event. And we're going to go ahead and update the state, this.setState. And what about the state needs to change? Well, I need to change either base or other, depending upon which select menu I selected. And the way I get at which one I selected is via this name attribute. The base select menu has a name attribute of base. And the other select menu has a name attribute of other. So if I want to set the state, I want to set the key of whatever event.target-- remember, event.target is the select menu itself-- whatever event.target.name is. If it's base, I went to update the base. If it's other, I want to update the other thing. And what do I want its value to be? I'll go ahead and say event.target.value. So, OK, let's give this a shot. I refresh the page. I have US dollars. If I change it to something else, all right, it actually changes. If I change this one, that value changes as well. So I'm now able to change these select drop-downs. So I've got the drop-downs. Let me go ahead and now add the text fields. So I have these select menus. And I also want an input field, whose value is this.state.value. Recall that inside of our state we're storing a value called value, which is going to be the base currency value, and a value called converted, which is going to be the converted currency value. So inside this input field we're going to store this.state.value. And inside of the converted field, we'll go ahead and also store input, whose value is this.state.converted. And I'm going to add an additional attribute to this input field called disabled. A disabled input field just doesn't allow you to edit it. I only want people to edit the top input field, not the bottom one. So if I said disabled equal to true, that's not going to allow anyone to edit the converted field, only the base currency field. So I refreshed that. And great, now we have something that looks a little bit closer to what we might expect, whereby I have drop-downs, where I can choose the currency. I can choose a currency. And I have these two input fields, one of which I could actually type things into, and one of which is none, not changing. Of course, this input field, much like the input fields we've seen before, if I actually try and type something into it, nothing's actually happening. It's just staying at 0. And the reason for that is I need to add some sort of change handler to say when I type something into the input field, we need to actually update the state of the application. We need to change what's inside this field. So on this input field I'll add onChange attribute. And that's going to be equal to this.changeValue. Again, changeValue's just an arbitrary name I'm choosing. But it's going to be the name of the function that's going to change the value of the input field. So here's that changeValue function. And changeValue's also going to update the state. And it's going to set the value to event.target.value for updating the value inside of the state anytime I try and change the input field. So now I have an interface that works. I can choose different currencies from the drop-down menu. I can type numbers, or letters theoretically, into this input field. But the currency conversion isn't actually happening. So this is going to be the last step of actually doing the currency conversion when I type something into the input field. And so how are we going to do that? Well, I want some function that's going to recalculate the converted value based on all the information inside of my state. So before I do anything about the interface, let me just add that function. I'm going to add a recalculate function, which is going to be a function that is going to take care of the process for me of making the API requests to exchangeratesapi.io and figuring out what the exchange rate is, multiplying it by the amount of value that I want to convert, and then displaying that value on the page. So let me go ahead and first say this.state.value is the input field where I'm storing information about what it is that I've typed into the base currency input. I'm going to go ahead and parse that into a number because right now it's a string. ParseInt is a function we've seen done in order to parse an integer. But of course, the thing that I type into the input field might not be an integer. It could have a decimal point. I could be trying to convert, like, $2.80, for example. And so I want to say parseFloat instead of parseInt to say treat this number as a floating point number. So it's not a function we've seen before. It behaves basically the same way that parseInt does. And I'll say that inside of a variable that I'm going to call value. Questions so far about what I'm doing or why I'm doing it? All right, there's a chance that this value is not going to be a number. And so there's a special value or special function in JavaScript called isNaN, in other words, is not a number, that takes an argument and just tells you whether it's a number, whether it's not a number or not. So for example, if I pass value into isNaN, it'll return true if value is not actually a number. And so that's useful for me because if it is the case the value is not a number, then I don't want to actually do any more calculation. I just want to return. We're done here. So, so far, I've taken whatever I typed into the input field, tried to parse it as a floating point number, saved that value inside of this variable called value. And if it's not a number, then just go ahead and exit the function. There's nothing else that we want to do here. But if we keep going, if it is a number, then let me go ahead and run a fetch query, whereby I say OK, let's fetch from https://api.exchangeratesapi.io/latest. Base is going to be-- and then I'll plug in a value here. And the value I want to plug in is this.state.base. Again, this code, basically the same as the JavaScript code that we were using before in order to do the same thing without React. But I'm making an API request via fetch, requesting data from a server. I can type in URL here to try and request data from that server. And right now I'm getting stuff from Exchange Rates API. But you could imagine getting it from something different. You could run a Flask web application, for instance, hosted on Heroku and try and fetch something from that Flask web application. And so here we can use the React front end to combine with just about any back end. Here I'm using the Exchange Rates API back end. But it could be any Flask web application that you create or web application server that you create in any other language. It doesn't really matter. React will just be able to request information from that back end. So I fetch it. And then the syntax here was a little bit strange. But you might recall that we then said, all right, then let's take that response and convert it to JSON. And then with the resulting data, do something with that data. Was there a question here? Yeah? AUDIENCE: [INAUDIBLE] SPEAKER: Yes, so these here are back ticks around the string. And the reason I'm using back ticks is that's how JavaScript does format strings, whereby if I want to insert a value into the string, like this.state.base, you surround the string with back ticks. And then you use dollar sign and curly braces to insert a value here. It's effectively the same as putting the lowercase f in front of a Python string to insert values there. Every language just deals with format strings a little bit differently. We get back the data. And if we remember what the API response looks like, it comes back as a big JSON object that looks something like this. And it has a key called rates. And inside of that key called rates is this big object, where a key is going to be some currency, like New Zealand dollars. And the value is the exchange rate, 1.47 New Zealand dollars for every 1 US dollar. And so once I have this data inside of data.rates and then indexing into rates this.state.other, that's going to get for me the exchange rate, because the inside of data, we're going into rates. And inside of rates, I want whatever the other currency is. That's the exchange rate that I want. So I want to go ahead and say this.setState. Remember that converted was the name of the part of the state that referred to whatever the value of the converted dollar amount was. And I'm going to set converted equal to data, get at the rate, get at the exchange rate. Is this going to work? Is this done? There's a mathematical bug here. Yeah? AUDIENCE: You need to multiply it by [INAUDIBLE].. SPEAKER: Yeah, I need to multiply it by a value. Because if I want to convert $2, then I want to take whatever the exchange rate is for $1, and multiply it by 2 to get whatever the converted amount actually is. So the converted value's just going to be whatever the exchange rate is multiplied by the value. So I'm never actually calling this recalculate function anywhere, although this recalculate function does now hopefully actually work in terms of getting the exchange rate, multiplying it by whatever the input value is, and then updating it. So what I'd like to do is say, any time I change the value in the input field, let's go ahead and recalculate. And so you might imagine doing something, like, OK, this.setState and then this.recalculate, might be an easy thing to imagine as reasonable logic to do. Let's update the state, setting the value to event.value.target, and then do the recalculation. There's a small problem, though, in terms of the way the JavaScript works. A lot of JavaScript is asynchronous, meaning that we might be running this code, this.setState, and then immediately run this.recalculate, even if the state isn't yet done being updated. Like if it happens to be taking the browser a bit of time to update the state, this.recalculate, in theory, could be run before this.state is done. Like, this.state is going to be executed first, but it could take some time separately and asynchronously. And we might end up running this.recalculate too early. And so this is a common enough paradigm in React that there's some special syntax for doing this. This.setState can actually take two arguments. The first argument is the change that you want to make to the state. But you can also pass to this.setState a function that you want to run after the state is done updating, not something that we saw yesterday. But if I say, all right, let's set state to this new value, and then give this.setState another argument and say, all right, this.recalculate, what am effectively saying here is, yeah, go ahead and set the state, setting value equal to event.target.value. But when you're done setting the state, then run this this.recalculate function. And that will happen only after the state is done executing. So it's this optional additional argument I can give to this.setState to run some calculation after the state has fully updated. And so that's what I'd like to do. When I set the state, I'd like to recalculate after I've done that update. So let me go ahead now and refresh the page. And we'll give this a try. 0 US dollars equal to 0 euros. I'll go ahead and delete and type in the number 1. And we see the number of euros corresponding to 1 US dollar. I type in the number 2, and I see the number of euros corresponding to 2 US dollars. Amazing. Where's a bug in this application? Not quite perfect yet. Yeah? AUDIENCE: But if you change [INAUDIBLE]. SPEAKER: Great. If I change the currency, if I say, OK, I don't want to convert doing euros, I want to convert between Chinese yuan, for example, and I change it, nothing about the input field changed. And why is that? Well, when I made a selection via this makeSelection function, I'm updating the state, updating the value of the drop-down. But I'm not doing the recalculation. So to fix this, I probably want to also say, after you make a selection, let's go ahead and also compute this.recalculate in order to recalculate the correct value. So I'll go ahead and run this again. 0 US dollars, 0 euros. If I say 2 US dollars, I get that number of euros. If I change it to Japanese yen, then I see, OK, now we see an update for the number of Japanese yen that corresponded to US dollars. And it works the other way around too. I can go here and change this to Canadian dollars, and it changes to show me how many Japanese yen are equal to 2 Canadian dollars. Questions about any of that and how it worked? All right, a couple changes that we could theoretically make here. One thing is if I have 2 Canadian dollars and I delete it, like just turn it into the empty string, we end up with a situation where the number beneath stays the same. So you could add some logic here in order to try and handle this particular situation, whereby I could say, when I change the value of the input field, if ever the converted field-- I can update the converted field as well, whereby-- well, actually, I'll backtrack a little bit more. Right now what's happening is if I make a change, like change from 2 Canadian dollars to 22 Canadian dollars, there's going to be some sort of lag here, whereby I'm making a request to the server in order to make requests from the Exchange Rates API. And when I make that request to the Exchange Rates API, then it's going to update this field. And so what I might like to do is instead of just leaving the converted field as is, change it to something like calculating or some sort of message to indicate that it is calculating the exchange rate before it actually gives me back what the exchange rate actually is. And so to do this, I could do something like this. When I change the value of the input field, let me also set the value of converted equal to null, for example, meaning let me just clear out the value of the converted field, set it equal to null. And then in this input field, rather than give it just a value of this.state.converted, let me say is this.state.converted equal to null? If it is, then let me just say, calculating. But otherwise-- and again, this is the ternary operator that we took a look at yesterday. Otherwise, then it can be this.state.converted. So a little bit of extra logic here. But the logic on line 39 is saying what should show up in that lower input field if my converted value has null, meaning I haven't given it an actual number yet? Then go ahead and display the word calculating. But if there is an actual number there, then go ahead and just display that number. And so now the result is going to be-- and the API seems to be working rather quickly today. So maybe we won't be able to notice this. But if I type 2, and I change it to 21, yeah, there's a brief moment where you can see that it's calculating before it actually updates with what the correct answer is. So there's never a state where I see conflicting information that the other field just hasn't yet updated. It will always just say calculating until I've calculated the number. Yeah? AUDIENCE: [INAUDIBLE] SPEAKER: This.state.converted is part of the state that represents whatever the value of the converted currency is. And I have that just because I defined it initially in the state. I called it converted. And I'm updating it whenever I want a new value for that input field. The only small corner case here is that now if I delete and I press Delete again, go to an empty input field, the other field is just going to say calculating forever because it's never going to end up getting a number. So you could go through and you could fix this by adding a couple more conditions. You can see the code in the source code examples that I posted online that'll help walk you through that as well. So we now have an application that can do currency conversion between different currencies. You can type in different amounts of currency and see the converted value. You can choose different things from the drop-down to convert between different currencies but there is some inefficiencies here still. Any sense for where the inefficiencies might be or room for improvement? So one thing you'd might notice is that any time I change the input field, or anytime I select a new currency from the drop-down, we're running the recalculate function. And the recalculate function is going to parse the number, reach out to the API via web request, get back some response, parse that data, and save it inside of the input field. Which part of this might not be necessary to do every time I type something into the input field? Yeah? AUDIENCE: If you're just changing the number, but you're keeping the two currencies the same? SPEAKER: Yeah, if I'm just changing the number, like if I just change this from 28, press Delete, OK, now 2 US dollars, I'm making yet another web request to ask the API, like, what is the exchange rate between US dollars and euros? And then getting that number, which is almost definitely going to be the same number that I got like a second ago when I just did the last calculation. And I'm reaching out again to get that number and then doing something with that number, which is costly in a number of ways. It's costly on part of the this web application because I'm reaching out to the server when I probably don't need to. And it's costly on the part of the API because it's getting more requests than it needs to handle because there's some unnecessary requests here. Now, sure, it's possible that I typed in a number, and I don't type in another number until, like, tomorrow, by which point the currency exchange rates might change. But you might imagine that if I make two requests for the same exchange rate within, like, a minute of each other, odds are that it's totally fine to just use the old exchange rate and not have to recalculate or re-request from the API what the exchange rate actually is. And so here we're going to implement what's called a cache, a way that we can just store information locally rather than need to reach out to an API in order to access that information. And what we'd like to say is some sort of model, whereby we can store exchange rates, and if I try and request the same exchange rate within a minute of the last time that I requested that exchange rate, then don't bother making another API request. Just use the data that we've stored. Question, yeah? AUDIENCE: Is there a limit on the API? Like, if you just [INAUDIBLE] make the request every time [INAUDIBLE].. Is there a certain point where the API [INAUDIBLE]?? SPEAKER: It depends upon the API. Certain APIs impose formal rate limiting, whereby you can only make a certain number of requests per hour or per day, for example. This API happens to not be like that, though they do have a disclaimer that warns, like, please cache your results, which is what we're about to do, so as to avoid bogging them down with many, many requests for constant currency exchange rates. So it does depend a little bit upon the API. Let's try and actually implement this cache. And you can see this implemented inside of exchange1.html, if you take a look at the source code examples for today. So up here at the top, we have this.currencies, and we also have this.state. Let me go ahead and add another variable called this.cached, which is going to store a JavaScript object where the keys are going to be currency exchange rate bases that I've stored. And the values are just going to be those currency exchange rates that I have actually stored inside of my web browser's memory, in this particular case. So all the interesting logic is not-- no longer in the interface. The interface is not changing at all. The only thing that's changing is my recalculate function. How am I doing this recalculation? Well, let me say, when I get this data back, before I set the state, let's update this.cached. Let's update what's inside of the cache, in particular at the key this.state.base, meaning update inside the cache. I need a different value in the cache for every different base currency, because I want to be able to store different possible base currencies. And I'm going to store two things. I'm going to store the exchange rates, which is going to be in data.rates. And again, this is just a JavaScript object. And I'm also going to store a timestamp, meaning at what time did I insert this data into the cache? At what time that I make the initial request? And save that data. And in JavaScript, there's a special function called date.now, which will just get us a date object that represent the current timestamp. So what's happening here? I'm making a request to the API, taking the resulting data, and storing it inside of the cache, saving it inside of some variable, whereby that variable is going to store the exchange rates, and it's also going to store the current timestamp, the timestamp for right now. And then we're going to go through with what we did before of setting the state, updating it to whatever they exchange rate is times the dollar amount that we're trying to convert. But now that we have this cache, we can add some logic before we make this API request so that we don't need to always make this API request. In particular, we can add an if statement. I want to check to see whether or not-- well, I want to check to see two things. One, is this base currency already in the cache? In which case, I don't need to make another API request. And if it is already in the cache, has it been less than, say, a minute since the last time that I made this request? So this is the idea of what's called cache invalidation, the idea that after a certain point in time, this cache is going to be considered stale or invalid, and I want to go ahead and make the API request anyway. If I make the-- if I check again tomorrow, the exchange rates are probably change, so I probably want to get the most up-to-date exchange rate. But within a certain period of time, like a minute, it's probably not going to change much. And so how am I going to do this? Well, if it is in the cache, it'll be in this.cached, this.state.base, right? This variable is the same as this. This is where I'm storing the information if it is, in fact, inside the cache. And let me first check to make sure it's not equal to undefined. If I try and index into a JavaScript object into a key that doesn't exist, the value I get back is a special value called undefined. And so if it's not equal to undefined, that means there's actually something in the cache. But I also want to make sure that it's been at most a minute since the last time that I updated the cache. And so how am I going to do that. Well, let's take the current timestamp, date.now, and let's subtract the timestamp in the cache, so the current timestamp minus this.cached, this.state.base-- that's what's in the cache-- dot timestamp to say get at the timestamp key of whatever is in the cache. And so I'm taking the current date, subtracting the time at which point I put the data in the cache. And as long as that is less than a minute, this subtraction is going to return to me a number in milliseconds. So a minute is going to be 1,000 milliseconds times 60 seconds. So long as the difference between now and when the time I put things in the cache is less than 1 minute, then let's go ahead and draw the information from the cache. And so to do that, we'll go ahead and say, this.setState, setting the converted value. Originally, the converted value was equal to this value, data.rates, this.state.other times value. But it's no longer going to be stored in data.rates. Instead of data, the information is stored in the cache, so this.cached, this.state.base, get at the exchange rates in the cache, convert it to the other currency, and multiply it by the value. So fair amount of code here. We'll zoom out, see if you can see what's going on here. We have a condition. It's running a check. The first part of the check is saying, is there actually something in the cache for this currency? Take the cache, look up the base currency. If it's undefined, there's nothing in the cache, so we can't do anything. So we're checking to make sure there's actually something there. And if there is something there, let's make sure it's recent enough to use. Let's take the current timestamp, subtract the time that we put the data in the cache, and make sure it's less than 1 minute, 1,000 milliseconds times 60 seconds. Assuming this cache is valid and we can use it, we'll set the state, updating the value of the converted input field and say, all right, let's take the value of the cache for this base currency, get the exchange rate for the other currency, and multiply it by whatever value was typed in. At that point, we don't need to go on with the function, we've already been able to update the state. So I can just hit Return and say exit the function now. A lot of code going on there. I encourage you to take a closer look at it. It's on the course website. And if I open up exchange.html now, the first time I type in currency, like 1 US dollar, it's going to calculate for a moment. But now if I update it, say, 12 US dollars, the update's almost instantaneous. You don't see calculating and then it changes, because there's no longer this additional couple milliseconds of latency of going to the server, requesting the exchange rates, and then using that information. It's just going to be using the cache. It's going to compare to whatever current value is in the cache to say this is within a minute. Let me just go ahead and update myself to go ahead and use the value that's stored inside of the cache. Questions about that idea of why we did it, of how we did it? Yeah? AUDIENCE: Yeah, so just in terms of the syntax, I'm a little bit confused. When you have this.cached bracket this.state.base, bracket dot rates, when do you use period, and when do you use brackets when you're [INAUDIBLE]?? SPEAKER: Great question. When do you use periods when you're going into a JavaScript object? When do you use the brackets? Long story short, they're basically interchangeable, whereby if I go into the console here, and I have a JavaScript object-- I'll call it const object-- that has a key of A and a value of 2 and a key of B and a value of 8, for example, I can say object.a to get at the A property of the object. Or I could also say, object square bracket and then in quotation marks "B" to get at the B property of the object. And those will work the same way. Generally speaking, if there is a fixed name of a property of the object that I want, like I know it's A, for example, I'll just use the dot notation, object.a, to get at the A property. But sometimes my program doesn't know in advance what the property is. Like, I have some variable called key, which is set to B, for example. And if I want to access the key property, I can't say object.key. That's undefined. That's looking for something inside of my object with a key that's literally called key. If key is a variable and I want to look it up inside the object, I'll need to do object square bracket key to say, all right, let's get at the key property of the object. So you could just use square brackets for everything. But the dot notation sometimes just makes things a little bit cleaner. And so I'll use that too. But, yeah, good question and good clarification there. Other things? Yeah? AUDIENCE: Do you [INAUDIBLE] differences between cache and local storage? SPEAKER: Yeah, good question. So cache, local storage, cookies, what is the difference between all these things? So a cache can come in a number of different forms. Your computer, CPU, has a cache that it uses when it's reading things from memory, for example, that is lower level than what we're dealing with in this class. A cache you can think of as just a general term for any way of storing data in a place that's easier to access, for instance. So you might-- your web browser, for example, probably has a cache for web pages, whereby when it's loading a web page, it could just reload the web page that it has in the cache rather than try and request the whole web page again. And so caches come in many different forms, and it's a very general term. The type of cache we're using here is just the cache that's being stored inside of our browser's memory that's going to store the exchange rates. But if I were to close this page and reopen it, that cache would be wiped clean because I reset the value of the cache. Every time I construct a brand-new example of an app, in this case, in this exchange rate program. Local storage, meanwhile, you can think of as a type of cache whose job it is, is to store information inside the browser, in particular so that information can be used by my application later if I open it up at a different time. It's especially useful for being able to store information inside the browser that will persist even when I close the page and reopen it. Because otherwise that probably wouldn't be the case. And that's all happening on the client side, on the front end. Cookies, meanwhile, you can think of as having to do with the interaction between the server and the user, the client, whereby if you have a cookie, you can think of it as a way of the server keeping track of who you are, such that if the user, the client, is sending a cookie along with every web request it makes to the server, if the server sees that cookie multiple times, it knows, OK, I know who this individual is based upon the value of the cookie, for example. There are also ways for cookies to store information about the state of the current user's interaction with a server, though we haven't really touched on that in this class. Yeah? AUDIENCE: Just a quick question. Why would this.currencies in this.cached have their own separate variables rather than including them in this.state? SPEAKER: Good question. So why is it that this.currencies, this.cached are separate? Currencies is a separate because it's not really something about the application that is going to change, insofar as it's basically just a fixed list of variables. I could have effectively pulled this out of it entirely and just made it a constant variable inside my JavaScript, like said, const currency equals something, for instance. And the cache is sort of separate because nothing about what the application looks like is really dependent upon the cache. It's only dependent upon the values of base, other value, and converted. And our recalculate function uses the cache to give converted a value. But the interface itself is only based upon the value of the converted thing. So a couple of things I'll talk about briefly, the first of which is going to be options that you'll have for the morning project. I'll talk about the morning project first before I dive into one last example. And because we are reaching-- well, actually, sorry. Before I talk about the morning project, one thing that I should mention is how React is typically used in practice, whereby so far, when we've been writing React code, we've been writing this React code purely inside of this HTML page, where we've been including in the JavaScript section some JSX code, JSX code being the version of JavaScript that allows me to have HTML elements embedded inside the JavaScript. This is not normal JavaScript code and not code that our browsers natively are able to understand. And this is why we've been including this Babel package up here in the header section of our page, which is going to take care of the job of transpiling our code from JSX into plain-old JavaScript so that our browser can understand it. In practice, this is not the type of program that you would want to deploy, at least not in this form. And in fact, when you open up any of the problems we've been doing so far, you'll see this warning that says, you are using the in-browser Babel transformer. Be sure to pre-compile your scripts for production. And what that warning is basically saying is rather than deploy something that is JSX code plus the Babel transformer, that's basically translating code from one language to another, and have that translation process happen every single time someone opens a page on your web page, we can just pre-compile all of those scripts once. Before we release our application for production, we can say, go ahead and take all of that JSX code, compile it into plain-old JavaScript code, and let's just deploy the JavaScript code. That way we compile it once. And then anyone who uses our page can just open the page normally, no translation necessary because it's already in plain-old JavaScript. And so there are a number of tools for being able to do this. But perhaps the most common and most popular is a special program created by Facebook called Create React App. In order to use Create React App, you'll need to install something called Node or Node.js on your computer. Node.js is just what's called at JavaScript runtime. You can think of it as a way of getting JavaScript code to run just about anywhere. You can use JavaScript on the server, in addition to using it just on the client. But if you install Node.js on your computer, you can then get access to a program called NPM, the Node Package Manager. And there's a particular package called Create React App, which is going to build for you a React app that has a lot of scripts, useful scripts and tools that are already built into it. And so you're not going to need this for the purposes of this class. But I wanted to show it to you. Because if you ever go into production in order to build applications with React, you're probably not going to be just putting it in the script section of your HTML page. You're probably going to be doing something a little more along these lines. So once you install Create React App, you can create a new React application by just typing create-react-app, followed by the name of the application. So I want to create an application called Hello. And again, you're going to need to install Node and then install Create React App in order for this to work. But I type create-react-app Hello. It's just the name of an application. And then the Node package manager is going to go through a long step of fetching all these packages and useful scripts that it's going to use for me. And it's going to build for me the basis of an application written in React. And it's going to give me a starter application, basically, a foundation on which I can start to build. Once it's done, I'm going to CD into the Hello directory. And if I type that last and look what's in here, there's all sorts of stuff that's actually in here. I have public, which is going to store a bunch of files that our web application might use, a source folder, where all the JavaScript code is actually going to be located, Node modules, which is a special folder for including other packages, much in the same way that in Flask we would import-- in Python, we would import other modules that we might use. In Node, you can also have modules that you install to give you additional capabilities. But once you're in here, the way to run this React application is just to say NPM run start to actually start running this application. And what that's going to do is it's going to start up this web application, and it's going to give me a default React application that's just going to display, I think, the React logo. Yeah. So this is the default React application that React gives you if use Create React App. But the nice thing about this is that it automatically has separated things into files for me. So that if I go into Hello and go into Source, my source folder has an app.js file, where they're defining an app component that, again, has a render function that says editApp.js and save to reload. And so this is already getting into the structure of what most React applications look like, which is rather than having all of the components inside of the same HTML page, have a different app.js file for the app component and have something else dot JS file for some other component and just separate all of your components into different files. It separates the CSS into different files as well and just starts the process of trying to separate things out. If you use Create React App, it also has dynamic reloading, the way that your Flask app might do. But you don't even need to refresh the page. Instead of editSource.app.js and save to reload, I can replace this with Welcome to CS50 Beyond and save that. And without doing anything, if I just go back to Chrome, my page is already updated with whatever it is that I changed. So every time you save, Create React App will take care of the process of auto reloading or hot reloading the web page up in order to reflect whatever I've changed, so very useful for development, such that you can just make a change, save it, and the page will automatically reload to reflect those changes. So this is very helpful for development. But when I'm ready to actually build this program in order to ship it, for example, I would run something like NPM run build inside of my Hello directory. And that's going to run a special script that Facebook has put together that's going to basically take care of the process of taking all this code in different files and written in JSX and compile them for me into regular JavaScript that any web server can understand. And so if I go into the build directory now, I see that I have all of these files here that are basically just going to be files that I can serve, using any old web server without the need to translate things from one language to another. And that will just work as well. So not going to delve too much into that because there's a lot of details and nuances here that are worth exploring, and this changes from time to time. But just good to be aware of, in case you decide to continue with building React applications. Questions before I go on about anything? All right, so this morning's project, you're going to have a number of options. I'm going to introduce one of them in just a moment, so we're not quite done for the morning lecture just yet. But a number of options for things you can do. One of the goals here is just to continue giving you an opportunity to continue working on React. So I know many of you are still working on or adding features to your to-do list application or your flashcards application. So if you'd like to, feel free to continue working on and adding to those applications. If you'd like an interesting new challenge, if you happen to be done with those, or you're getting bored of them and want to try something different, you can try implementing a Connect Four game, sort of an upgraded version of tic-tac-toe, this time for four in a row instead of three. But I'll show you what that looked like in just a moment. And if you'd like to-- we're on the second-to-last day now. We want to give you the flexibility to explore, the freedom to try things new. Feel free to start something new of your own choosing. Really the goal of today is to really make the focus be about project time. You've heard me talk a lot this past week. So we're going to try and talk a little bit less today and give you more of an opportunity to really try things hands on, because you've probably found by this point in time that really the best way to learn this material is to be working with it. Try things out. Try and get features to work on your own and see what happens ultimately. And so these are going to be the potential options. If you choose to implement the Connect Four game, the types of features you might want to consider, displaying a board and which player's turn it currently is. When the user clicks on a column, the turn changes from red to black or black to red, as the typical colors are for Connect Four. If you click on a column, that drops a circle inside of that column, for example. Don't allow clicking on a column that's already filled, if it's filled to the top of what's usually a seven-by-seven grid. And when someone has four in a row, you can display who the winner is. And that will probably be the last step. If you choose to try and implement this project, when it's done, it's probably going to look something-- it could look something-- a little something like this, whereby you have a Connect Four and who the current turn is in a big seven-by-seven grid. And this, again, could just be an HTML table, for example. I've implemented on mine some additional features for detecting when the mouse is happening. And there are things like onMouseEnter. There's event handlers you can do for that so that when you hover over columns, that you can see the column highlighted, though you don't need to implement the feature, at least not initially. You click on a column, and, OK, that drops a red circle into that column. And, OK, now it's black's turn. And so black can click somewhere in order to play a black circle there. And we can continue this game until someone gets four in a row. And when someone gets four in a row, the winner of the game is black, and we display who the winner is then. So an option for something that you can try to implement if you're looking for an interesting challenge. And let's think about the type of state that you probably want to store inside of this application. What state would you want to store? So even if you're not planning on programming this, let's at least plan it out. Think in React. Think in our minds about what the state of this looks like, what sort of event handlers we would need for it, what is the structure this is going to be? Yeah? AUDIENCE: [INAUDIBLE] SPEAKER: Yeah, a list of lists probably makes sense. We've got a seven-by-seven grid. And you have this interesting idea, which is probably a good one, which is that each list might want to be a list of all of the columns, like a column of lists for the first column, the second column, the third column. Because that's going to make it easy to implement the idea of dropping a circle into one of the columns, where if we have this is just like an array that is black and then red, if I try and add something to this column, it's as simple as just appending something to the end of that array, for instance. You just add to that array. And that's going to result in this being reflected. Now, if you design it with each column being a different array inside of your application, you'll need to think a little bit about how to make rows appear inside of your table, for instance. But, yeah, that's good thinking. So an array of arrays storing in the state to store the current state of the board, what else are we going to need to store in the state? Whose turn it is, great, is it red's turn? Is it a black's turn probably also something you want to store as well. And what sort of event handlers do we need on this board? How does the user interact with this page? We've seen a lot of event handlers. We've seen the onChange event Handler for when someone typed something into an input field, for example. What else have we seen that might be useful here? AUDIENCE: [INAUDIBLE] SPEAKER: On click, yeah. And you probably want something like that, that for each of these table cells, we have some sort of on-click mechanism for if you click on this table cell, well, that's going to correspond to dropping something in this column. And there's probably no difference between clicking here and clicking here. Because either way, it's still clicking inside of the same column, given the nature of the game. And so you can think about, all right, when someone clicks on a cell, you probably want to ask a question, OK, what column is it in? Then you might want to ask a question like, is the column already filled? In which case, well, we can't add anything more to it. But if it's not already filled, then we can say something. All right, let's go ahead and add something to that particular array inside of our application state. So these are the sorts of questions, the sorts of things to be thinking about. Before you even write a single line of code, think about, what are the components of the application? What is the state of the application going to look like? What sort of event handlers are you going to need? And this is the way to begin thinking about things inside of React. Yeah? AUDIENCE: Is that [INAUDIBLE]? Or is [INAUDIBLE]? SPEAKER: Good question. The way I have implemented this, and the way you can certainly consider doing it, is this is just an HTML table. But inside of each table cell, I've inserted an SVG target, inside of which is just going to be an SVG circle. And as you remember, a circle just has the center x-coordinate, center y-coordinate, radius, and also a fill color. And these are just fill color red and fill color black. And if you really want to get fancy and implement the hover feature, all the hover feature is, is when you're hovering over a column, let's go ahead and turn the cells gray. And if there's nothing in a cell, rather than having nothing there, have a white circle instead of a red circle or a black circle. And by doing that, you're able to get this effect of this hover, where there's just white circles across the entire grid. But when you hover over a column, the background turns gray. And so you get the effect that you might expect, that looks visually interesting, but really is just a bunch of circles and colors changing in terms of the way this is actually implemented. If you're looking for a simpler version of this, feel free to try and take the tic-tac-toe application that we originally made in Flask and just reemployment that using React. And that might be a good starting point as a place to begin and then building up to something like this. But there are a lot of possibilities. And so goal for today is really to give you a lot of time for hands-on practice in order to work on that. So questions about morning project? All right, so a couple things on a logistical note. So we only have today and tomorrow left in CS50 Beyond. Just see you all know, tomorrow's probably going to be a short day. We're probably going to wrap up probably around mid-day and not have an afternoon session tomorrow, so wrap a bit early, give you most of Friday to have off in order to enjoy the weekend before classes begin again on Monday. And one other thing that I'd like to ask you all to do before you-- right now before you actually start working on the morning project is to fill out our feedback form. So just try and get this some feedback before the course is over. If you go to cs50.ly/feedback, you'll find an anonymous feedback form, where you can leave anonymous feedback about your experience in this class over the course of this week. Goal for this is just very good useful data for us in order to be able to help improve the class. This is the first time that we are offering CS50 Beyond. And so all of this is sort of brand-new curriculum and content and organization. So curious to get your impressions on what you think the strengths were, what things you would change, or what things you would recommend as improvements for the future. We'll definitely read all of this feedback, and so all of it would definitely be very helpful. So please go ahead and fill out this feedback form. When it's done, feel free to dive into your projects. We'll work on these projects now in the morning, here in this auditorium, until about 12:30, at which point we'll break for lunch. We'll come back at 2:00 for a couple of more points, but mostly to spend the afternoon focused on working on projects. And so we'll break for now and let you work on those projects.
B1 中級 在React中思考--2019年以後的CS50》。 (Thinking in React - CS50 Beyond 2019) 2 0 林宜悉 發佈於 2021 年 01 月 14 日 更多分享 分享 收藏 回報 影片單字