Тёмный

Stop Using useEffect With Redux! 

Jack Herrington
Подписаться 184 тыс.
Просмотров 25 тыс.
50% 1

Опубликовано:

 

12 сен 2024

Поделиться:

Ссылка:

Скачать:

Готовим ссылку...

Добавить в:

Мой плейлист
Посмотреть позже
Комментарии : 147   
@dira4734
@dira4734 3 месяца назад
Just using this pattern in various projects for a team over and over, I want to refactor everything now
@finallyfriday2888
@finallyfriday2888 3 месяца назад
looks good, maybe a new video on managing middlewares as the app grows i think that would be great. as for this handling errors too?
@josemcgomes
@josemcgomes 3 месяца назад
I've just used this on my current project, it's easier to grasp than I imagined. Thanks!
@geralt36
@geralt36 3 месяца назад
This looks kinda complicated. Is there a benefit of doing this over using a hook which contains all this fetching and updating logic and can be shared across components? I know you mentioned keeping all the store logic in the store but imo this method isn't very intuitive. And either way in an actual project we'd probably be using something like react query or something similar anyway.
@jherr
@jherr 3 месяца назад
Separation of concerns and the ability to fully unit test the store without also testing the UI at the same time are pretty sizable benefits. Technically speaking lots of stuff "works", but I personally like to set the bar higher than "working" and into well factored and organized code with clear separation of concerns.
@markerikson3383
@markerikson3383 3 месяца назад
Different tools, different needs. Sometimes you do need truly "global" app behavior that isn't isolated to one component or isn't limited to a component lifecycle, and that's what listeners give you.
@geralt36
@geralt36 3 месяца назад
@@markerikson3383 ah right that makes sense. Thanks
@marouaniAymen
@marouaniAymen 3 месяца назад
Thanks, I liked the vide because I learned a new concept/technique in Redux Toolkit (which is by the way my favourite State Manager for React), but I didn't run into this anti-pattern in my work projects because I did not save the query information on the store, I consider this a quite bad design choice, anyway, this video will be useful in case I find myself obliged to handle a case like shown.
@shakhruzrakhmatov2285
@shakhruzrakhmatov2285 3 месяца назад
we don't change redux store by firing action inside useEffect but we trigger the middleware by firing action inside the useEffect, what do you think is it also anti pattern? Unfortunately we can't trigger middleware the way you are doing because we want to trigger it only when specific page loads, so we fire the action inside the useEffect to run the middleware only when certain page loaded.
@shakhruzrakhmatov2285
@shakhruzrakhmatov2285 3 месяца назад
@RealRhythmandPoetry didn't really get what you meant. what we do here is, we have a middleware which listens to action named `homePageLoaded` and we trigger this action only once inside the useEffect when our HomePage component gets rendered for the first time. Now how we can get rid of useEffect in this case, how can I trigger this action only once and only when my HomePage component gets rendered?
@danteDeveloper
@danteDeveloper 3 месяца назад
Thanks for video Jack. I prefer fetch api on event handler. It makes more sense to me dispatch actions which cause side effects should be deal with user interaction handler.
@bass-tones
@bass-tones 3 месяца назад
What event is going to run associated with “app start,” or “component mounted?” Not all API calls can be tied to user events.
@jherr
@jherr 3 месяца назад
Do what works for you. The point of this video was to cover an often-overlooked option in Redux toolkit. Though I will say, the unit test case is legit here. If you want clean separation of concerns between the business logic and the presentation logic, this listener functionality provides another clean way to do that.
@MrChernicharo
@MrChernicharo 3 месяца назад
Oooof! This one caught me off guard... suddenly I want to rebuild everything I've done last year from scratch...
@BraedenSmith
@BraedenSmith 3 месяца назад
I feel like this scales pretty poorly if there's anything non-trivial in our predicate when comparing old vs new state? We scale with order N comparison functions that have to run after every action. It feels like we'd want to strongly prefer the action-style predicate because it's guaranteed to be cheap.
@FinnGamble
@FinnGamble 3 месяца назад
The way you implemented debounce is very elegant!
@karlnewgrove
@karlnewgrove 3 месяца назад
Maybe it needs something like listenerApi.cancelActiveListeners() before delay to act like a 'real' debounce, e.g. restarts the delay on subsequent keypresses while the delay is still running?
@hesam-init
@hesam-init 3 месяца назад
hey jack thanks for video i have a question do we have something like this in zustand ?
@jherr
@jherr 3 месяца назад
Yeah, you can `subscribe` to the Zustand store directly and you'll get called back when anything changes. `useMyStore.subscribe(() => ...);`
@dkaigorodov
@dkaigorodov 3 месяца назад
Love this video, use jotai thought, I need to try it. Wondering about jotai debouncing
@oakleyorbit
@oakleyorbit 3 месяца назад
I use rtk for a large application I’m building, is it the best probably not but to think people wouldn’t use something anymore just because it’s not trendy is kind of ridiculous.
@matttamal8332
@matttamal8332 3 месяца назад
*laughs/cries in redux-sagas*
@BraedenSmith
@BraedenSmith 3 месяца назад
This is so easy with redux sagas too! It's exactly the same backing mechanism, and gives you better granularity over your state machine.
@fatih-araz
@fatih-araz 3 месяца назад
not readable code for bigger projects. I do not understand, why i schould load the middleware if i do not need it currently. It depends on what u need in your code. Thank you for this way of using middleware
@jherr
@jherr 3 месяца назад
Is anyone you to load the middleware if you don't need it?
@Stephen0208
@Stephen0208 3 месяца назад
This is a very interesting way to do things that I have never considered before.
@sierafa1nt
@sierafa1nt 3 месяца назад
It's look like inventing overcomplicated version of an effector 😁.
@SeanCassiere
@SeanCassiere 3 месяца назад
TanStack Router mentioned! 🙌🏼
@saharshg
@saharshg 3 месяца назад
this is amazing and really easier to test with this pattern!!
@echobucket
@echobucket 3 месяца назад
Why wouldn't you just dispatch inside the event handler for typing into the field? Why are we over complicating this?
@jherr
@jherr 3 месяца назад
It's just an example to show use of the listener. You don't need to use it in this use case. But now that you know it's available you might be able to find other uses for it. FWIW though, I have seen code that looks an awful lot like what I showed here, in the wild.
@AviCharlop
@AviCharlop 3 месяца назад
It is a nice clean solution but I don't see how this solution scales to the scale that many codebases that use redux need. Would every slice have a middleware also?
@markerikson3383
@markerikson3383 3 месяца назад
That entirely depends on you and your application. Listeners are there to add side effects, so the real question is how many of your slices also have associated side effects? Beyond that, note that data fetching should really be handled by RTK Query, not listeners. Jack showed off a listener that did data fetching as a small example, but he did say "we're intentionally _not_ using RTK Query" here. I would guess that most slices do _not_ have much in the way of associated side effects.
@tariksadiku4723
@tariksadiku4723 3 месяца назад
Wait, why? Why not just wait until the search is completed and dispatch an event there?
@jherr
@jherr 3 месяца назад
This is a simple example just to demonstrate this overlooked feature of RTK. I've seen a lot of code like the example here. It's very common for folks to use `useEffect` whenever they have async behavior that they need in their store.
@akrishnadevotee
@akrishnadevotee 3 месяца назад
Hi Jack! Have you used jotai in large production apps? How does it compare to zustand and the likes? I really like jotai and couldn't find a reason to move away from it
@jherr
@jherr 3 месяца назад
Meta uses Jotai/Recoil in their applications. You could probably call those large. Personally I find Zustand a little easier to reason about. The data and "methods" are organized together. You can persist it as a unit. etc. Where Jotai is a graph with data dependencies. I think in one way Jotai scales better because you don't end up adding more data and dependencies to a store. But on the other hand, the graph could get out of control. Either way this really comes down to architecture and code reviews and actually caring about the code. As long as you do that, you're probably ok using either approach.
@MarlonEnglemam
@MarlonEnglemam 3 месяца назад
I had no idea that was possible, that's amazing.
@onthefence928
@onthefence928 3 месяца назад
i just dont understand, why use redux in an age of context and hooks?
@Edgars82
@Edgars82 3 месяца назад
I guess you have not been working on large and complex react projects?
@onthefence928
@onthefence928 3 месяца назад
@@Edgars82 I’m an enterprise react developer and I’ve actually been working with my team to remove redux from a legacy project to modernize it with hooks instead.
@eskimojo14thefirst
@eskimojo14thefirst 3 месяца назад
@@onthefence928 because context still has no mechanism to selectively subscribe to a portion of the context value, so you either end up with a million contexts or components rerendering unnecessarily
@lalithrockz
@lalithrockz 3 месяца назад
Redux is not legacy, class components are right? From what ive read redux is used when context api is not enough ​@@onthefence928
@BraedenSmith
@BraedenSmith 3 месяца назад
Context is untenable for any state that isn't extremely stable, and requires you to scale horizontally to avoid massive performance issues
@maykl_tlkn
@maykl_tlkn 3 месяца назад
Thank you for great video. It was helpful!
@wishmeheaven
@wishmeheaven 3 месяца назад
Thanks, Jack. How does this mechanism integrates with RTK Query?
@jherr
@jherr 3 месяца назад
RTK Query sends events as actions just like everything else, so those actions could potentially trigger listeners.
@fatehboug1932
@fatehboug1932 3 месяца назад
I don't agree with this approach, i suggest using rtk query if ur using redux it makes things easier.
@jherr
@jherr 3 месяца назад
As mentioned in the video, RTK query is the ideal. I agree. I intentionally decided not to use it so that I could show this feature instead. This feature is NOT a replacement for RTK Query. It can be used in conjunction with RTK Query.
@congminhluu5068
@congminhluu5068 3 месяца назад
so what if RTK Query is involved? Should we still include that listenerMiddleware? Also I'm not sold on this approach since it's also just another kind of side effect listener and those tend to get to the point of unmaintainability really quick. The action of getting value + debounce should be in the UI file in a handleInput or something, you then delegate the api call + store update into another function
@pataroque
@pataroque 3 месяца назад
With rtk query, you'll not need this 98% of the time. I disagree with you on the debounce. You want to debounce the request, not the user interaction, ergo that kind of logic has no place in the UI layer, it should go in the data layer of the application.
@congminhluu5068
@congminhluu5068 3 месяца назад
@@pataroque normally you want to debounce the action of causing the request, not the request itself. (For an search input, the text still shows as the user types, but the API call will be deferred. This is common practice). Then the problem comes down to where do you want to put down this logic. From my perspective, no side effects functions are ever easy to maintain or work with, whether you put that in useEffect or redux's thing. Just for this simple pokemon search this demo demonstrated already involved some really niche functions and wayy more code lines then needed. Imagine this for 10 or more complex forms.
@omkarkulkarni3644
@omkarkulkarni3644 3 месяца назад
Would've been exhaustive if the initial state setting was done without tanstack router's method A lot of apps don't use it/ What's the normal way of doing it ?
@tanercoder1915
@tanercoder1915 3 месяца назад
When's the next live? I'd like to see full live on this. Too fast 😮. Can you also please show how to do this with rtkquery?
@ohyesthelion
@ohyesthelion 3 месяца назад
Does the test hit the real api or is that one result in the list mocked?
@romansernato6526
@romansernato6526 3 месяца назад
a well timed info sir)
@ashish_prajapati_tr
@ashish_prajapati_tr 3 месяца назад
this is awesome.
@valeriofunk5000
@valeriofunk5000 3 месяца назад
everybody gangsta until auth workflow with query listeners
@lucasgiunta8874
@lucasgiunta8874 3 месяца назад
Im so happy to migrate to react router v6 with the loader/actions and jotai for some specific store. Redux is overkill.
@fottymutunda6320
@fottymutunda6320 3 месяца назад
What's the theme you're using in your shell?
@whoman7930
@whoman7930 3 месяца назад
its oh-my-zsh
@fottymutunda6320
@fottymutunda6320 3 месяца назад
@@whoman7930 I reckon oh my zsh has many themes, no?
@jherr
@jherr 3 месяца назад
It's the atomic theme from oh-my-posh.
@ShaneCodes
@ShaneCodes 3 месяца назад
I see a really similar pattern used with react query, people will make a useEffect depend on a value from a query hook variable isLoading, isFetching, data etc. useEffect(() => { //do something to the data }, [query.data])
@tharunbalaji5131
@tharunbalaji5131 3 месяца назад
just asking it out of curiosity what is the font and theme that your using ?
@alextalos6141
@alextalos6141 3 месяца назад
the theme i think is night owl
@oscarrojas3968
@oscarrojas3968 3 месяца назад
If we would like to use rtk query for that case we would use a lazy query and call it whenever search term changes ? In that case an useEffect would be necessary ?
@jherr
@jherr 3 месяца назад
No, I think even in the code it states that the ideal thing to do here would be to use RTK Query. This code is just for the purposes of the example.
@Lambdaphile
@Lambdaphile 16 дней назад
@@jherr Yes, but you would still have to use the triggerPokemonSearch callback returned from useLazyQuery inside useEffect or the change handler. I think that’s what he’s asking.
@ealipio
@ealipio 3 месяца назад
how will be the scenario with react query?
@albertdugba
@albertdugba 3 месяца назад
I'm very guilty of this. Now I know better
@user-cv9qt6zd5z
@user-cv9qt6zd5z 3 месяца назад
It feels like I should still use effects if I only need to to fetch once on page load, correct me if I'm wrong.
@jherr
@jherr 3 месяца назад
If you really want it run on app start, then just fetch it as part of the store initialization.
@mateuspamaral00
@mateuspamaral00 3 месяца назад
What do you think about RxJS? for me its a great choice instead of redux
@jherr
@jherr 3 месяца назад
IMHO, I would not use it as a state manager in React. Angular currently uses it and they are moving away from it. It's an awesome library. Unfortunately there is just a lot of congnitive overhead for little gain. There are simpler state managers that provide just as much functionality.
@pataroque
@pataroque 3 месяца назад
​@@jherr I agree with you on the cognitive overhead, but for large, complex, monolithic apps, the set of operators and abstractions Rxjs provides is unparalleled. Angular is moving away from it, but essentially is just simplifying the dev experience with signals. IMHO, both paradigms have the same goal - making the UI truly reactive, and I prefer them both over stores. In observables/signals you push data to a stream, and the data source does not care, who's listening. With stores you pull the data, and there's no knowing if you got the correct state at the moment of asking.
@z3rocodes
@z3rocodes 3 месяца назад
Interesting!
@delta4v
@delta4v 3 месяца назад
Nah, if we can create a listener on a go when the component mount & remove it when the page does not use it any more then I will probably use this. The pattern is created so people can follow the common case but not to follow it blindly
@dolanont
@dolanont 3 месяца назад
Thank you so much.
@kiiro5738
@kiiro5738 3 месяца назад
thank you for this tips ! Can you do a video about React Server components? with some example
@jherr
@jherr 3 месяца назад
You should check out my other videos. There are a TON on RSCs and the NextJS app router.
@kiiro5738
@kiiro5738 3 месяца назад
@@jherr oki thank you for your work :) it helps me a lot !
@danieljohnmorris
@danieljohnmorris 3 месяца назад
pokemon best friends forever
@Dev-Phantom
@Dev-Phantom 3 месяца назад
cool
@sahajranipa
@sahajranipa 3 месяца назад
Final nail in the coffin for why not to use useEffect()🤣🤣🤣🤣👎👎👎👎👎😞😞😞😞😞
@notramiras
@notramiras 3 месяца назад
You can absolutely use useEffect if you're not moving out your reactive logic out of the React tree, as you tend to do it with Redux and other client-side state containers. But it's simply better to use a query container and you're fine with occasional useEffect logics.
@waleedsharif618
@waleedsharif618 3 месяца назад
Pokemon is back !
@airixxxx
@airixxxx 3 месяца назад
After seeing all that boilerplate I'm so grateful for not using Redux anymore
@roni_castro
@roni_castro 3 месяца назад
that looks complex and unecessary, to be honest.
@imdevbutok
@imdevbutok 3 месяца назад
Ótimo vídeo. Como ficaria isso com zustand? 🐻
@jherr
@jherr 3 месяца назад
Seria muito mais simples com Zustand. Você pode assinar a loja diretamente usando o método subscribe e fazer o que quiser no método subscribe.
@weichen3994
@weichen3994 3 месяца назад
I got a even better idea, just use react query instead! You can thank me later
@diegoulloao
@diegoulloao 3 месяца назад
Stop using react, use svelte, in first place
@mostafahamid9479
@mostafahamid9479 3 месяца назад
actually i am not a big fan of functional programming and prefer to use OOP, and design patterns instead
@arielbatista7ify
@arielbatista7ify 3 месяца назад
That doesn't go with react
@mostafahamid9479
@mostafahamid9479 3 месяца назад
@@arielbatista7ify why? because people write functional?
@arielbatista7ify
@arielbatista7ify 3 месяца назад
@@mostafahamid9479 is design to be written with functions, the funtional components and the hooks, that would be half of the app, then the state manager, most of then also functional, so what part will you write with oop? Wouldn't make sense to write the remaining parts also with functions?
@jherr
@jherr 3 месяца назад
Gotta agree with @arielbatista7ify on this one. Idiomatic React is far more of a functional programming paradigm than an OOP paradigm. Certainly since the release of hooks. Class components have effectively been deprecated for years. The new docs hardly mention them outside of Error boundaries, which are the only time you'd ever need them. And I can think of a single state store that is built on classes. I'm not saying that's good or bad, it just is what it is. If you try to write React in an OOP style you are going to be swimming upstream. Angular (?) may be more aligned with OOP.
@Zagoorland
@Zagoorland 3 месяца назад
Or just stop using redux in the first place? :p
@codefinity
@codefinity 3 месяца назад
👍🏾 It’s overkill 👍🏾 99.8 % of the time. There 😯 many steps to go before reaching for Redux.
@arielbatista7ify
@arielbatista7ify 3 месяца назад
Exactly
@bass-tones
@bass-tones 3 месяца назад
This problem isn’t exclusive to redux. How does using zustand, for example, avoid this issue?
@codefinity
@codefinity 3 месяца назад
@@bass-tones 🤷 This video is on Redux. 🤔 there will be another on Zustand.
@bass-tones
@bass-tones 3 месяца назад
@@codefinity I realize. But the topic of this video is NOT a reason to not use Redux. In fact, unlike something like zustand, at least Redux offers a solution to “effect”-like code at the store level. Doing this in any of the minimal store libraries would require a completely custom solution, or falling back to something like useEffect like this video discusses, which I agree is a bad idea. If anything, this is a point in Redux’s favor that its structure allows for concepts like listeners.
@egorovsa
@egorovsa 3 месяца назад
All is easier. Just stop using Redux
@ergusto
@ergusto 3 месяца назад
How about just stop using Redux....
@Endrit719
@Endrit719 3 месяца назад
Do you think it's worth using redux in this age, I think not
@jherr
@jherr 3 месяца назад
I've played with it, but haven't used it. I don't see a lot of projects opting for it and instead going with Redux/RTK or lighter weight options like Zustand, or atomic solutions like Jotai/Recoil. I'd stay away from MobX/Valtio as they are incompatible with the new React Compiler.
@jherr
@jherr 3 месяца назад
IMHO, it's a well maintained state library with a great ecosystem, a massive install base, a healthy weekly downloads (9M) and a huge number of developers working with it. It's also very well maintained and it keeps up with the standards. So those are a lot of positives. That being said, I think there are legitimate cases where no state manager at all is required. Or where you can use something lighter weight, like Zustand or Jotai/Recoil. There is no single "right answer" here, you have to evaluate state management and project architecture choices in the broader view of all kinds of factors.
@ivan.jeremic
@ivan.jeremic 3 месяца назад
if you ever need a state manager then use jotai or recoil they are built for react style programming.
@yassinerassy6840
@yassinerassy6840 3 месяца назад
You can just use redux saga for that
@markerikson3383
@markerikson3383 3 месяца назад
We specifically designed listeners to be a simpler and easier-to-use replacement for sagas :)
@devinsights8021
@devinsights8021 3 месяца назад
react query simple and better, redux thunk or rtk query
@jherr
@jherr 3 месяца назад
Yeah, I think I mentioned that as I was talking and also in a comment in the code. This is a simple example from which you should be able to extrapolate your desired behavior.
@kikevanegazz325
@kikevanegazz325 3 месяца назад
Great tutorial, as usual. Global stores are supposed to be distributors. However, this listener seems to be bringing some responsibility to the store. Am I missing something?
@jherr
@jherr 3 месяца назад
Hmmmm, I personally don't think of stores that way. I look at the store as the Model and Controller in the Model-View-Controller paradigm. It should contain global data as well as the business logic to maintain that data. And you should be able to have unit tests on just the store that test whether mutations run against the store follow the business logic in updating the store. If that's not the case, and you have business logic in the UI, then what you have with the store is effectively a fancy global variable.
@baldcoder_
@baldcoder_ 3 месяца назад
Stop using Redux.
@grigorykuimov
@grigorykuimov 3 месяца назад
this is a bad example. It's better to use rtk query. use a better example to avoid teaching people bad practices
@jherr
@jherr 3 месяца назад
This is a bad comment. It's better to express some gratitude for the free content to avoid showing people your lack of manners.
@kunaldhuria3935
@kunaldhuria3935 3 месяца назад
@@jherr 😂😂😂😂
@grigorykuimov
@grigorykuimov 3 месяца назад
I appreciate you for the other videos. Especially the explanations of react-compiler. In this case I saw how redux was not showing its best side. Sorry for my comment above
@jherr
@jherr 3 месяца назад
@@grigorykuimov No worries. I also apologize for my flippant response. It's not like me. Was just having a bad day.
@markerikson3383
@markerikson3383 3 месяца назад
Jack's goal was to show off the fact that the listener middleware exists, and the basic mechanics of how to use it. He did specifically note that in a real app, you should use RTK Query for the data fetching part, but the "fetch on user input" example worked nicely to show the basic mechanics _and_ that you can do async behavior like the equivalent of a debounce.
@MrYerak5
@MrYerak5 3 месяца назад
Stop using redux
@PhilipAlexanderHassialis
@PhilipAlexanderHassialis 3 месяца назад
You don't always get the chance to pick your libraries. Sometimes you have to maintain existing code and augment it in the least pervasive way possible. This is a very good example on how to do that particular use case on an existing redux codebase.
@jherr
@jherr 3 месяца назад
Personally I don't use a lot of Redux, but many many... many... codebases do. In fact, the majority of code bases I've worked with and audited use Redux. And many of them use this anti-pattern for using async code with their Redux stores.
@Inegxd
@Inegxd 3 месяца назад
rxjs epic state$.pipe(map(selector),....
@Siddiskongen
@Siddiskongen 3 месяца назад
Are people still using redux? 🙈
@jherr
@jherr 3 месяца назад
9M downloads a week, and though it has flatlined a little in 2024 it's still chugging along. Redux came along a couple of years after React was released and there is a lot of ecosystem around it. Lots of folks use it and it's well maintained and keeps up with the changes to React over time. I personally don't use it for my projects. I tend to just use hooks. Or use something like Zustand or Jotai. But I can't deny that there is a LOT of Redux code out there. Thus I cover it.
@odra873
@odra873 3 месяца назад
Sure our 250 developers project that’s in development for years will just throw out redux because Zustand is hype of the week, after that let’s just rewrite everything to jotai?
@Siddiskongen
@Siddiskongen 3 месяца назад
@@jherr Exactly. I use hooks. Every senior frontend developer i know use hooks. We all tried Redux between 2016 and 2020 and we all concluded it was a global state beast that results in bad frontend architecture. Data should be scoped to the context boundary its needed. Redux just pushes all data in a big state tree mess. Suddenly you are afraid to remove or change reducers because the you are unsure what components are affected by the global state. Can also add on the issue with "memory leak" issue since the redux store is global and if components dont clean up after them self there are lot of dead data in the global store eating up memory in the browser, so you need to enforce strickter code reviews to ensure obsolete state is always removed.
@dacam29
@dacam29 3 месяца назад
People live in the fantasy world of to-do apps 😂
@juanantonionavarrojimenez2966
@juanantonionavarrojimenez2966 3 месяца назад
Yep. And proud of use it. Great library and great maintainer.
Далее
Are Your React Components Too BIG?
12:20
Просмотров 23 тыс.
The Story of React Query
8:55
Просмотров 103 тыс.
Learn React Hooks: useCallback - Simply Explained!
17:15
Five React App Killing Anti-Patterns 🪦😱
12:47
Просмотров 32 тыс.
Should you still be using Redux in 2023?
7:35
Просмотров 52 тыс.
ShadCN’s Revolutionary New CLI
12:11
Просмотров 27 тыс.
No const! How NOT To Give A JavaScript Talk
10:28
Просмотров 61 тыс.
Goodbye, useEffect - David Khourshid
29:59
Просмотров 499 тыс.
I Was Wrong About React Router.
19:06
Просмотров 60 тыс.