반응형

들어가기 앞서...

npm start 및 개발을 위해 시작을 하는데 터미널에서 watchmanResponse가

 error: 'std::__1::system_error: open:/ 파일경로 : Operation not permitted'

와 같이 나온다면 

$ watchman watch-del-all
$ watchman shutdown-server

터미널에 입력해주자

원인은 macOS의 보안 제한 사항으로 watchman이 디스크 엑세스 권한을 받지 못한 상태이다.

입력후 다시 실행시켜 권한요청을 확인눌러준다.

Animated

Animated를 import를 하고 원하는 값을 상수로 선언해준다. animate에 사용할 value는 state에 들어가면 안된다.

const Y = new Animated.Value(0);

절대 직접적으로 값을 변경할 수 없다.  예를 들어  

 

let Y = new Animated.Value(0);
y = 20

절대 불가 절대 금지

값은 Animate API를 통해서만 변경 할수 있다.

 

또한 Animate에는 아무것에나 animation을 줄수 없다.  가능한 것은 다음과 같다

Animated.Image
Animated.ScrollView
Animated.Text
Animated.View
Animated.FlatList
Animated.SectionList

또한 위에 없는 것을 사용하고 싶다면 createAnimatedComponent()를 사용해야한다. 

 

만약 styled-components와 함께 사용하고 싶다면 다음과 같다.

const Box = styled(Animated.createAnimatedComponent(View))`
 // 스타일링
`;

또한 다음과 같은 방법도 있다.

const Box = styled.View`
//스타일링
`;
const AnimatedBox = Animated.createAnimatedComponent(Box);

 

Modify Animated Value

다음 세가지가 animated 의  값을 변경할 수 있다.

초기 속도로 시작해서 점점 느려지고 멈춤
Animated.decay()

물리 모델을 제공함.
Animated.spring()

대부분의 경우 timing을 사용함
Animated.timing()

 

Animated.timing() / spring() / decay()

()의 값에는 두가지가 들어가야한다. 하나는 value 다른 하나는 configuration이다.

configuration에서 보낼 수 있는건 첫째로 toValue가 있다.

toValue는 value로 부터 어디까지 가는지를 정하는것이다. 

useNativeDriver는  animation에 대한 모든 데이터와 관련된것이 시작하기 전에 animation에 대한 모든 것을 native 쪽으로 보내준다. 이것은 꼭 true로 값을 해두자

 

spring()에는 bounciness / speed / tension / friction이 들어간다. 또한 bounciness와 speed를 같이 사용할 수 있고 tension과 friction이 같이 사용가능하다.

 

timing()에는 duration, easing, delay, isInteraction도 있다.

Animated Active

Animated.timing().start()
Animated.timing().reset()
Animated.timing().stop()

 

BIG TIP

TouchableOpacity나 Highlight component를 직접적으로 움직이는 것을 되도록 삼가하는게 좋다. 이들은 자체적인 animate가 있기에 animation이 부자연스럽게 작동한다. 

 

new Animated.Value를 사용하게 되면 애니메이션이 활성화면서 Value값으로 돌아오게 되는 이슈를 가질수있게 될것이다. 이럴때 다시 렌더링이 일어나더라도 value를 유지 하게 해주는 hook인 useRef를 사용해주는게 좋다.

const Y = useRef(new Animated.Value(0)).current

 

Interpolation

Y의 값을 input으로 가지고 다른 값으로 변환하는것이다.

예를 들어 [-10 , 10]으로 움직이눈 물체가 있다고 가정하자. 우리는 가운데 0의 값 , 즉, [-10, 0, 10] 값 중 0에 크기를 키운다던지 투명도를 준다든지 따위의 값으로 [1,0,1] 바꿔주는것이다. 

interpolate는 두개의 옵션을 받는다. 

하나는 inputRange이다. 기본적으로 어떤 값들을 가져올것인지 뜻한다.

다른 하나는 outputRange 이다. 가져온 값을 다른 값으로 반환해주는 것.

또한 두값의 길이는 같아야한다.

const AnimatedBox = Animated.createAnimatedComponent(Box);

export default function App() {
  const [toggle, setToggle] = useState(false);
  const Y = useRef(new Animated.Value(200)).current;
  const toggleClick = () => setToggle((prev) => !prev);
  const moveUp = () => {
    Animated.timing(Y, {
      toValue: toggle ? -200 : 200,
      useNativeDriver: true,
      easing: Easing.inOut(Easing.cubic),
    }).start(toggleClick);
  };
  const opacityValue = Y.interpolate({
    inputRange: [-200, 0, 200],
    outputRange: [1, 0, 1],
  });
  return (
    <Container>
      <TouchableWithoutFeedback onPress={moveUp}>
        <AnimatedBox
          style={{transform: [{translateY: Y}], opacity: opacityValue}}
        />
      </TouchableWithoutFeedback>
    </Container>
  );
}

 

Animated.ValueXY

값을 두개를 지정할 수 있다.

const POSITION = useRef(new Animated.ValueXY({x: 0, y: 300})).current;

Animated.sequence / Animated.loop

  const {width: SCREEN_WIDTH, height: SCREEN_HEIGHT} = Dimensions.get("window");
  const POSITION = useRef(new Animated.ValueXY({x: 0, y: 0})).current;
  const topLeft = Animated.timing(POSITION, {
    toValue: {x: -SCREEN_WIDTH / 2 + 100, y: -SCREEN_HEIGHT / 2 + 100},
    useNativeDriver: true,
  });
  const bottomLeft = Animated.timing(POSITION, {
    toValue: {x: -SCREEN_WIDTH / 2 + 100, y: SCREEN_HEIGHT / 2 - 100},
    useNativeDriver: true,
  });
  const bottomRight = Animated.timing(POSITION, {
    toValue: {x: SCREEN_WIDTH / 2 - 100, y: SCREEN_HEIGHT / 2 - 100},
    useNativeDriver: true,
  });
  const topRight = Animated.timing(POSITION, {
    toValue: {x: SCREEN_WIDTH / 2 - 100, y: -SCREEN_HEIGHT / 2 + 100},
    useNativeDriver: true,
  });
  const moveUp = () => {
    Animated.sequence([topLeft, bottomLeft, bottomRight, topRight]).start();
    /**
     Animated.loop(
      Animated.sequence([topLeft, bottomLeft, bottomRight, topRight])
    ).start();
    */
  };

 

 

DRAG

panResponder API

기본적으로 손가락의 제스쳐나 움직임을 감지할 수 있게 해준다.

panResponder에는 많은 function들이 있다. 그 기능들은 panHandlers 안에 묶여져있다. 그중 가장 중요한 function은 onStartShouldSetPanResponder이다. 만약 true를 리턴해주면 이벤트가 지정된 곳에서 터치를 감지하기 시작할 것이다.

 

onPanResponderMove에는 event와 gesture 인자가 있는데 그중 gesture 안에는 여러가지가 내포되어 있는데 가로와 세로축의 움직임을 감지하는 dx , dy 가 있다.    *** (x으로 부터 d 만큼 y로부터 d만큼 이동했다.)

 

onPanResponderRelease 는 손가락을 떼면 동작하는 기능

export default function App() {
  const {width: SCREEN_WIDTH, height: SCREEN_HEIGHT} = Dimensions.get("window");
  const POSITION = useRef(
    new Animated.ValueXY({
      x: 0,
      y: 0,
    })
  ).current;

  const rotation = POSITION.y.interpolate({
    inputRange: [-200, 200],
    outputRange: ["-360deg", "360deg"],
  });
  const borderRadius = POSITION.y.interpolate({
    inputRange: [-200, 0, 200],
    outputRange: [100, 0, 100],
  });
  const bgColor = POSITION.y.interpolate({
    inputRange: [-200, 200],
    outputRange: ["rgb(255, 99, 71)", "rgb(71, 166, 255)"],
  });

  const panResponder = useRef(
    PanResponder.create({
      onStartShouldSetPanResponder: () => true,
      onPanResponderMove: (_, {dx, dy}) => {
        POSITION.setValue({
          x: dx,
          y: dy,
        });
      },
    })
  ).current;
  return (
    <Container>
      <AnimatedBox
        {...panResponder.panHandlers}
        style={{
          transform: [...POSITION.getTranslateTransform()],
          backgroundColor: bgColor,
          borderRadius,
        }}
      />
    </Container>
  );
}

 

offset

opPanResponderMove를 사용해 POSITION의 값을 변경이 가능하게 되어 움직임이게 되면  클릭할 때 중앙으로 점프하는 모습을 볼수가 있다. 마지막 위치를 기억해주기 위해  onPanResponderGrant를 사용해준다.

  onPanResponderGrant: () => {
        POSITION.setOffset({
          x: POSITION.x._value,
          y: POSITION.y._value,
        });
      },

onPanResponderGrant 이 함수는 움직임이 시작될 때 부터 호출되어 터치를 놓을 때까지 추적하고 손가락을 놓으면 자리를 기억한다.

값은 ._value라고 작성해야 값이 넘어간다. 하지만 onPanResponderGrant을 계속 사용하게 되면 값이 계속 더해져서 움직이는 물체가 화면밖으로 나가게 된다. 이때 손가락을 놓을 때 그자리를 0으로 만들어주는 함수를 작성해야한다.

 onPanResponderRelease: () => {
        POSITION.flattenOffset();
      },
728x90
반응형

Array.of

무언가를 array로 만들고 싶을 때 사용한다.

const a = ["a","b","c","d"]

const a = Array.of("a","b","c","d")

 

Array.from

array lik object를 array로 만들어준다. 예를들어

//html
<div>
 <button class=button> iam btn</button>
 <button class=button> iam btn</button>
 <button class=button> iam btn</button>
 <button class=button> iam btn</button>
</div


const buttons = documenet.getElementsByClass("button")

// 이것은 불가능합니다.
buttons.forEach(btn=>{
  btn.addEventListener("click",()=>console.log("HI"))
}

//이것은 가능합니다.
Array.from(buttons).forEach(btn=>{
  btn.addEventListener("click",()=>console.log("HI"))
}

 

Array.find

find를 사용해서 true가 나올시 첫번째 element를 알려준다.

const fruits = ["mangoapple", "grape", "apple", "pineapple", "orange"];

const a = fruits.find((fruit) => fruit.includes("apple"));

console.log(a);


// mangoapple

Array.findIndex

true가 나올 시 해당 첫번째로 true값으로 반환된  인덱스번호를 알려준다. 

const fruits = ["mangoapple", "grape", "apple", "pineapple", "orange"];

const check = () => fruits.findIndex((fruit) => fruit.includes("apple"));

let target = check();
console.log(target);

const oneOfFruit = fruits[target].split("apple")[0];

const apple = "Apple";

fruits[target] = `${oneOfFruit} & ${apple}`;

target = check();

console.log(target); // <= target에 다음 해당 인덱스가 있으면 양수가 나오고 없으면 음수가 나온다.
console.log(fruits);


//["mango & Apple", "grape", "apple", "pineapple", "orange"]

 

Array.fill

const fruits = ["mangoapple", "grape", "apple", "pineapple", "orange"];

const check = () => fruits.findIndex((fruit) => fruit.includes("apple"));

fruits.fill("*".repeat(3), 1, 2);

console.log(fruits);

//) ['mangoapple', '***', 'apple', 'pineapple', 'orange']

 

Array Destructuring

const numbers = ['1','2','3','4','5']

const [one,two,three]=numbers

console.log(one,two,three)

//1,2,3





const numbers = ["1", "2", "3", "4", "5"];

const [one, two, three, six = "6"] = numbers;

console.log(one, two, three, six);

//1,2,3,4





const numbers = ["1", "2", "3"];

const [one, two, three, six = "6"] = numbers;

console.log(one, two, three, six);

//1,2,3,6

 

Renaming

//데이터를 받아왔다고 가정
const setting = {
  color: {
    chose_color: "dark",
  },
};
// 데이터의 이름을 renaming
//let으로도 가능하다.
const {
  color: {chose_color: choseColor = "light"},
} = setting;

// 위와 같다.
// const choseColor = setting.color.chose_color || "light";

console.log(choseColor);

//let으로 할경우
//데이터를 받아왔다고 가정
const settingTwo = {
  color: {
    chose_color: "dark",
  },
};
// 데이터의 이름을 renaming
let choseColorTwo = "blue";
({
  color: {chose_color: choseColorTwo = "light"},
} = settingTwo);

// 위와 같다.
// const choseColorTwo = settingTwo.color.chose_color || "light";

console.log(choseColorTwo);

 

Swapping & Skipping

//Swapping

let tomato = "lemon";
let lemon = "tomato";
//Array destructuring을 사용하여 값을 맞춰준다.
// 바꿀 값을 원래 순에 맞게 첫번째 배열에 넣어준다.
// 바뀌어져야하는 변수를 바꿀 값 배열에 맞게 순서대로 적는다.
//";"을 꼭 사용해서 라인을 분간 시켜준다.
[lemon, tomato] = [tomato, lemon];

console.log(tomato);
console.log(lemon);

//Skipping
const days = ["mon", "tue", "wed", "thu", "fri"];

const [, , , thu, fri] = days;
console.log(thu, fri);
728x90

'CODING PRACTICE > JavaScript & ES6' 카테고리의 다른 글

ES6__004(Promises / async &. await)  (0) 2023.12.21
ES6__003(Spread / Rest / for of Loop)  (0) 2023.12.20
ES6 __001 (Arrow Fuctions , String)  (1) 2023.11.21
Pagination 공식  (0) 2023.08.10
2023/04/11__JavaScript__05  (0) 2023.04.13
반응형

RN - Stlyed - Components

사용법은 웹과 비슷하지만 불러올때 /native 폴더 지정해주자

 

Adding TypeScript to Existing Project

npm install -D @tsconfig/react-native @types/jest @types/react @types/react-test-renderer typescript

프로젝트에 위 를 설치하고

Root에 tsconfig.json을 만들어

{
  "extends": "@tsconfig/react-native/tsconfig.json"
}

추가 해준다. 후에 나머지 파일을 모두 tsx로 바꿔준다. 제일 중요한 건index.js는 건들지 말자.

 

타입스크립트를 사용하다가도 .jsx를 사용하면 JS를 사용할 수 있다.

후에 styled.d.ts를 사용하기 위해 서는 웹과 방식은 동일하다

 

만약 theme color를 작성해놓은 ts파일이 style이면 바꿔주어야한다. styled.d.ts와 충돌이 일어난다.

 

React Navigation은 이미 가져올 수 있는 많은 Types를 제공해준다. 

 

 

Swiper - React Native Web Swiper 

스와이퍼 컴포넌트를 제공한다.

 

Expo BlurView - npx expo install expo-blur

IOS 개발할 시 npx pod-install 도 해야한다.

설치하고 npm run 을 다시 실행해야한다. native 요소에 접근한 것을 새로 설치한다면 무조건 해야한다.

 

React-Native - StyleSheet.absoluteFill

제일 많이 사용되는 패턴의 css가 제공되어진다.

<View style={StyleSheet.absoluteFill}>
	<Text>텍스트</Text>
</View>

StyleSheet.absoluteFill	은

const ContView = styled.View`
	position:absolute;
	width:100%;
    height:100%
`

같다

 

 

TIP. 날짜 관련

new Date('2021-12-20').toLocaleDateString("ko")
//2021.12.20
new Date('2021-12-20').toLocaleDateString("ko",{month:'long', day:'numeric', year:'long'})
//2021년 12월 20일

 

FlatList

ScrollView는 많은 도움을 주지만 application 퍼포먼스에 좋지 않다.

ScrollView는 모든 자식 컴포넌트를 한번에 render한다는 것이다. 그러니까 화면에 나오지 않는 부분까지 모두 로딩을 하고 렌더링 한다는 것이다. 그러기 위해서 FlatList가 등장한다. 이것은 Lazy하게 만들어준다.

FlatList는 렌더링하기 위한 perfomance interface이다.

또한 SectionList도 있는데 (https://reactnative.dev/docs/sectionlist) 링크에가서 확인하도록 하자 . IOS 어플 개발시 매우 유용할것으로 보임.

 <FlatList
          //FlatList 내부에서 스스로 map과 같이 처리해줌.
          data={trending}
          //renderItem은 function이고 1개의 인자를 받고 component를 반환해주면 된다.
          renderItem={({item}) => (
            <Trending
              backdropPath={item.~~~}
              originalTitle={item.~~~}
              voteAverage={item.~~~}
            />
          )}
        />

여기서 경고가 뜰 수도 있는데 VirtualizedList는 같은 방향의 ScrollView 안에 감싸져 있음 안된다.

FlatList는 Children을 가질수 없다.

수직방향의 FlatList를 수직방향의 ScrollView 안에 넣을 수 없다. 이것을 가능하게 하는것은 ListHeaderComponent 이다.

ListHeaderComponent는 컴포넌트를 모든 아이템 위쪽에 render해버린다.

unmount

우리가 탭을 눌러 컴포넌트를 떠나면 해주는 방법은  Tab.Navi에서 screenOption에 unmountOnBlue:true prop을 작성해주자

 

QueryClient

react query를 사용할때 한번에 refetch를 하는 방법이 있는데 refetch와 isRefetching을 사용해서 하는 방법도 있지만 코드가 길어진다. 그러기 위해서 useQueryClient를 사용해주면 좋다.

queryClient는 제일 큰 단위라고 생각하고 useQuery는 개별단위라 생각하자.

refetchQueries를 하면되는데 여기서 제일 제일 중요한 핵심포인트는 useQuery에서 key를 적어주는 곳에 배열을 만들고 첫번째에 묶어줄 카테고리를 작성하고 그후에 키를 작성해준다. 

const queryClient = useQueryClient();
 const {
    isLoading: nowLoading,
    data: nowData,
    isRefetching: isRefechingNowPlay,
  } = useQuery<IMovieData>(["movies", "nowPlay"], movieApi.getNowPlayingMovie);
  const {
    isLoading: trendLoading,
    data: trendData,
    isRefetching: isRefechingTrend,
  } = useQuery<ITrending>(["movies", "trendMoive"], movieApi.getTrendingMovie);
  const {
    isLoading: upcomeLoading,
    data: upcomeData,
    isRefetching: isRefechingUpcome,
  } = useQuery<IMovieData>(
    ["movies", "upcomeMovie"],
    movieApi.getUpcommingMovie
  );
  const activeRefresh = async () => {
    await queryClient.refetchQueries(["movies"]);
  };

 

728x90
반응형

Detective DarkMode (Theme)

useColorScheme / Apperance

Apperance module은 몇몇 function과 method를 제공해주는데 

 

이번에는 useColorScheme을 활용할 것이다.

 

import {useColorScheme} from "react-native";
function names(){
  const colorScheme = useColorSheme()
  return
}

 

시뮬레이터에서 ios에서는  shift + command + a 조합으로 모드를 바꿀 수 있음

안드로이드는 시뮬레이터 모바일 내에 setting으로 들어가 직접 바꿔주는 방법도 있는데 필자는 헤드 네비게이션을 내려서 퀵버튼을 편집하여 추가하였다. 

import {createBottomTabNavigator} from "@react-navigation/bottom-tabs";
import Movie from "../screens/Movie";
import Search from "../screens/Search";
import Tv from "../screens/Tv";
import {useColorScheme} from "react-native";

const Tab = createBottomTabNavigator();
const Tabs = () => {
  const isDark = useColorScheme() === "dark";
  return (
    <Tab.Navigator
      initialRouteName="Search"
      screenOptions={{
        headerTitleAlign: "center",
        tabBarStyle: {backgroundColor: isDark ? "#164863" : "#DDF2FD"},
        tabBarActiveTintColor: isDark ? "#DDF2FD" : "#FFC436",
        tabBarInactiveTintColor: isDark ? "#427D9D" : "#164863",
        headerStyle: {
          backgroundColor: isDark ? "#164863" : "#DDF2FD",
        },
        headerTitleStyle: {color: isDark ? "#FFC436" : "#427D9D"},
      }}
    >
      <Tab.Screen name="Movies" component={Movie}></Tab.Screen>
      <Tab.Screen name="Search" component={Search}></Tab.Screen>
      <Tab.Screen name="TV" component={Tv}></Tab.Screen>
    </Tab.Navigator>
  );
};
export default Tabs;

 

 

하지만 색상을 고려하는것에 많으 스트레스가 있다면 Theme에 대해 배워보자 @react-navigation/active에서 DarkTheme /  DefaultTheme

  <NavigationContainer
      onReady={onLayoutRootView}
      theme={isDark ? DarkTheme : DefaultTheme}
    >
      <Tabs />
    </NavigationContainer>

Tab Nav / ICON

한가지 방법으로는 tabBarIcon을 사용하는 것이다. screenOptions로 navigator에 두거나 screen에도 둘수 있는데

navigator에 두면 함수로 보내주어야하고 screen으로는 object로 보내줄수 있다.

  <Tab.Navigator
      initialRouteName="Search"
      screenOptions={{
        headerTitleAlign: "center",
        tabBarStyle: {backgroundColor: isDark ? "#164863" : "#DDF2FD"},
        tabBarActiveTintColor: isDark ? "#FFC436" : "#164863",
        tabBarInactiveTintColor: isDark ? "#427D9D" : "#164863",
        headerStyle: {
          backgroundColor: isDark ? "#164863" : "#DDF2FD",
        },
        headerTitleStyle: {color: isDark ? "#FFC436" : "#427D9D"},
      }}
    >
      <Tab.Screen
        name="Movies"
        component={Movie}
        options={{
          tabBarIcon: ({focused, color, size}) => {
            return <Ionicons name="film-outline" size={size} color={color} />;
          },
        }}
      ></Tab.Screen>
      <Tab.Screen
        name="Search"
        component={Search}
        options={{
          tabBarIcon: ({focused, color, size}) => {
            return <Ionicons name="search-outline" size={size} color={color} />;
          },
        }}
      ></Tab.Screen>
      <Tab.Screen
        name="TV"
        component={Tv}
        options={{
          tabBarIcon: ({focused, color, size}) => {
            return <Ionicons name="tv-outline" size={size} color={color} />;
          },
        }}
      ></Tab.Screen>
    </Tab.Navigator>

 

Stack Navigator

Stack Navigator는 새 screen이 이전의 screen 위로 올라오는 것이다.

두가지 옵션이 있는데 stack과 native stack 이 있다. 

stack navigator는 React Navigation에 있는 것이고. 자바스크립트로 구현된것이다. 익숙한 ios나 안드로이드의 모습으로 가지고 있다.

이게 중유한 이유는 각 Platform의 navigation을 사용하지 않는다는 것이다. 이것을 사용하면 커스텀이 가능하다.

 

native stack navigator는 native API를 이용해 구현된 것이다.

createNativeStackNavigator를 사용하여 navigator를 만들면 native로 만든 일반적인 어플리케이션과 완전히 같은 방식으로 작동하고, 퍼포먼스도 똑같다고 한다. native stack navigator를 사용하게 되면 커스텀 할 수 있는 영역이 약간 줄어들것이다. 왜냐면 이것은 ios와 안드로이드의 navigator를 사용하는것이기 때문이다.

npm install @react-navigation/native-stack
import {createNativeStackNavigator} from "@react-navigation/native-stack";
import {Text, TouchableOpacity, View} from "react-native";

const ScreenOne = ({navigation: {navigate}}) => (
  <View>
    <TouchableOpacity onPress={() => navigate("two")}>
      <Text>screen one</Text>
    </TouchableOpacity>
  </View>
);
const ScreenTwo = ({navigation: {navigate}}) => (
  <View>
    <TouchableOpacity onPress={() => navigate("three")}>
      <Text>screen two</Text>
    </TouchableOpacity>
  </View>
);
const ScreenThree = ({navigation: {goBack, setOptions}}) => (
  <View>
    <TouchableOpacity onPress={() => setOptions({title: "hihi"})}>
      <Text>Touch me</Text>
    </TouchableOpacity>
    <TouchableOpacity onPress={() => goBack()}>
      <Text>go Back</Text>
    </TouchableOpacity>
  </View>
);
const NativeStack = createNativeStackNavigator();

const Stack = () => {
  return (
    <NativeStack.Navigator>
      <NativeStack.Screen name="one" component={ScreenOne}></NativeStack.Screen>
      <NativeStack.Screen name="two" component={ScreenTwo}></NativeStack.Screen>
      <NativeStack.Screen
        name="three"
        component={ScreenThree}
      ></NativeStack.Screen>
    </NativeStack.Navigator>
  );
};
export default Stack;


//app.js
 <NavigationContainer onReady={onLayoutRootView}>
      <Stack />
    </NavigationContainer>
import {createNativeStackNavigator} from "@react-navigation/native-stack";
import {Text, TouchableOpacity, View} from "react-native";

const ScreenOne = ({navigation: {navigate}}) => (
  <View>
    <TouchableOpacity onPress={() => navigate("two")}>
      <Text>screen one</Text>
    </TouchableOpacity>
  </View>
);
const ScreenTwo = ({navigation: {navigate}}) => (
  <View>
    <TouchableOpacity onPress={() => navigate("three")}>
      <Text>screen two</Text>
    </TouchableOpacity>
  </View>
);
const ScreenThree = ({navigation: {goBack, setOptions}}) => (
  <View>
    <TouchableOpacity onPress={() => setOptions({title: "hihi"})}>
      <Text>Touch me</Text>
    </TouchableOpacity>
    <TouchableOpacity onPress={() => goBack()}>
      <Text>go Back</Text>
    </TouchableOpacity>
  </View>
);
const NativeStack = createNativeStackNavigator();

const Stack = () => {
  return (
    <NativeStack.Navigator
      screenOptions={{
        headerBackTitleVisible: false,
        headerTintColor: "pink",
        presentation: "formSheet",
        animation: "fade",
      }}
    >
      <NativeStack.Screen
        name="one"
        component={ScreenOne}
        options={{title: "1"}}
      ></NativeStack.Screen>
      <NativeStack.Screen
        name="two"
        component={ScreenTwo}
        options={{title: "2"}}
      ></NativeStack.Screen>
      <NativeStack.Screen
        name="three"
        component={ScreenThree}
        options={{presentation: "modal"}}
      ></NativeStack.Screen>
    </NativeStack.Navigator>
  );
};
export default Stack;

 

Combine Stacks & Tabs

solution .1

//app.js
    <NavigationContainer onReady={onLayoutRootView}>
      <Tabs />
    </NavigationContainer>
    
//Tabs.js
<Tab.Screen
        name="Movies"
        component={Stack}
        options={{
          headerShown: false,
          tabBarIcon: ({focused, color, size}) => {
            return (
              <Ionicons
                name={focused ? "film" : "film-outline"}
                size={size}
                color={color}
              />
            );
          },
        }}
      ></Tab.Screen>

Solution.2 

//app.js
    <NavigationContainer onReady={onLayoutRootView}>
      <RootNav /> //=> 새로 만든다.
    </NavigationContainer>
    
// RootNav.js
    <Navigate.Navigator screenOptions={{headerShown: false}}>
      <Navigate.Screen name="Tabs" component={Tabs} />
      <Navigate.Screen name="Stack" component={Stack} />
    </Navigate.Navigator>
    
// Movie.js
const Movie = ({navigation: {navigate}}) => (
  <TouchableOpacity
    onPress={() => navigate("Stack", {screen: "three"})} //=> 내가 갈곳으로 이벤트를 준다.
    style={{flex: 1, justifyContent: "center", alignItems: "center"}}
  >
    <Text>Movie</Text>
  </TouchableOpacity>
);

//Bottom Tab Nav
하단에 내가 갈 큰 카테고리
// Stack Nav
그 중간 카테고리

// 폴더를 구성한다고 생각하면 이해가 쉬울것 같음.
728x90
반응형

React Navigation

React Navigation은 React Native에서 navigation을 만드는데 독보적이다.

npm install @react-navigation/native
npx expo install react-native-screens react-native-safe-area-context

 

npx pod-install ios

아래 코드들을 android/app/src/main/java/<your package name>/MainActivity.java. 에 추가

create-react-native-app으로 프로젝트를 생성했으면 추가할 필요가 없다.

public class MainActivity extends ReactActivity {
  // ...
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(null);
  }
  // ...
}

 

 

import android.os.Bundle;

 

Tab Navigation

bottom-tabs navigation

(https://reactnavigation.org/docs/bottom-tab-navigator)

npm install @react-navigation/bottom-tabs
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

const Tab = createBottomTabNavigator();

function MyTabs() {
  return (
    <Tab.Navigator>
    // 모든 스크린에는 이름이 있다.또한 컴포넌트도 있다.
      <Tab.Screen name="Home" component={HomeScreen} />
      <Tab.Screen name="Settings" component={SettingsScreen} />
    </Tab.Navigator>
  );
}

 

import * as SplashScreen from "expo-splash-screen";
import {useFonts} from "expo-font";
import {Asset, useAssets} from "expo-asset";
import {Ionicons} from "@expo/vector-icons";
import {StatusBar} from "expo-status-bar";
import {useCallback, useEffect, useState} from "react";
import {Image, StyleSheet, Text, View} from "react-native";
import {NavigationContainer} from "@react-navigation/native";
import Tabs from "./navigation/Tabs";

export default function App() {
  const [ready, setReady] = useState(false);
  const [assets] = useAssets([
    require("./sky.jpg"),
    "https://blog.kakaocdn.net/dna/chdzI0/btqWU8aSuLk/AAAAAAAAAAAAAAAAAAAAAIfEz8Xsn-ZgISvRievor1l4l4_xl2Wkn0wbLCoBIKe-/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1761922799&allow_ip=&allow_referer=&signature=nPO4RTwHUOVETSbr3QphET%2F8yug%3D",
  ]);
  const [fontLoaded] = useFonts(Ionicons.font);
  useEffect(() => {
    async function prepare() {
      try {
        await SplashScreen.preventAutoHideAsync();
        await new Promise((resolve) => setTimeout(resolve, 3000));
      } catch (e) {
        console.warn(e);
      } finally {
        setReady(true);
      }
    }
    prepare();
  }, []);
  const onLayoutRootView = useCallback(async () => {
    if (ready) {
      await SplashScreen.hideAsync();
    }
  }, [ready]);

  if (!ready || !assets || !fontLoaded) {
    return null;
  }
  return (
    <NavigationContainer onReady={onLayoutRootView}>
      <Tabs />
    </NavigationContainer>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
});

 

Tab.Navigation Props

initialRouteName

initialRouteName은 첫번째로 렌더링될 route이다.

 

screenOptions

options으로는 많은 것들이 있다.

많은 것들을 option으로 만들려면 screenOption을 Tab.Navigator안에 사용해야한다. 

각각의 Screen에게는 options prop을 사용하면 된다.

const Tabs = () => (
  <Tab.Navigator
    initialRouteName="Search"
    screenOptions={{tabBarLabelStyle: {backgroundColor: "tomato"}}}
  >
    <Tab.Screen name="Movies" component={Movie}></Tab.Screen>
    <Tab.Screen
      name="Search"
      component={Search}
      options={{tabBarLabelStyle: {color: "green", fontSize: 20}}}
    ></Tab.Screen>
    <Tab.Screen name="TV" component={Tv}></Tab.Screen>
  </Tab.Navigator>
);
export default Tabs;

 

tabBarBadge

확인 안한 메세지 개수 뱃지 라고 생각하면 쉬움 문자열, boolean, null 도 반환됨

const Tabs = () => (
  <Tab.Navigator initialRouteName="Search">
    <Tab.Screen name="Movies" component={Movie}></Tab.Screen>
    <Tab.Screen
      name="Search"
      component={Search}
      options={{tabBarBadge: 5}}
    ></Tab.Screen>
    <Tab.Screen name="TV" component={Tv}></Tab.Screen>
  </Tab.Navigator>
);

tabBarActiveTintColor / tabBarInactiveTintColor

눌렀을때 활성화 색 / 비활성화 되었을 때 색

const Tabs = () => (
  <Tab.Navigator initialRouteName="Search">
    <Tab.Screen name="Movies" component={Movie}></Tab.Screen>
    <Tab.Screen
      name="Search"
      component={Search}
      options={{tabBarBadge: null, tabBarActiveTintColor: "#24aa31"}}
    ></Tab.Screen>
    <Tab.Screen name="TV" component={Tv}></Tab.Screen>
  </Tab.Navigator>
);
728x90
반응형

Ice Candidate (Internet Connctivity Establishment / 인터넷 연결 생성)

우리가 offer와 answer를 가질 때 그걸 받는 걸 모두 끝냈을 때, 그러면 peer to peer 연결의 양쪽에서 Ice Candidate라는 이벤트를 실해아기 시작할 것이다.

 

ICE Candidate는 webRTC에 필요한 프로토콜들을 의미하는데 다시 말해 ICE Candidate는 브라우저가 서로 소통할 수 있게 해주는 방법이다. 이 과정은 다수의 Candidate(후보)들이 각각의 연결에서 제안되고 그리고 그것을 소통하는 방식에 사용한다. 

 

Ice Candidate는 answer와 다른 모든 것들을 얻고 난 뒤에 일어나야 한다.

 

그래서 Ice Candidate event를 listen 해야한다.

  • RCTPeerConnection이 연결되고 바로 그 즉시 event를 listen하게 만듬
async function makePeerConnection() {
  myPeerConnection = new RTCPeerConnection();
  stream
    .getTracks()
    .forEach((track) => myPeerConnection.addTrack(track, stream));
  myPeerConnection.addEventListener("icecandidate", handleICECandidate);
}

function handleICECandidate(data) {
  //상대방이 방에 들어왔을 때 바로 동작한다.
  //상대방에게 candidate를 전부 보낸다.
  socket.emit("send_ice", data.candidate, roomName);
}

socket.on("received_ice", (ice) => {
  //상대방에게 candidate를 받아야한다.
  myPeerConnection.addIceCandidate(ice);
});

//server.js

wsServer.on("connection", (socket) => {
  socket.on("room_create", (roomName) => {
    socket.join(roomName);
    socket.to(roomName).emit("join_room");
  });
/* .... */
  socket.on('send_ice',(ice,roomname)=>{
    socket.to(roomname).emit('received_ice',ice)
  })
});

 

Senders(Add Track)

브라우저에서 서로의 존재를 확인 후에 캠 화면을 들어내게 해야하는데 track event를 등록해야한다.

const friendFace = documents.getElementById('friendFace')

async function makePeerConnection() {
  myPeerConnection = new RTCPeerConnection();
  stream
    .getTracks()
    .forEach((track) => myPeerConnection.addTrack(track, stream));
  myPeerConnection.addEventListener("icecandidate", handleICECandidate);
  myPeerConnection.addEventListener("track", handleAddTrack);
}

function handleAddTrack(data) {
  friendFace.srcObject = data.streams[0];
}

 

 

Bug Fix

다른 사람의 화면을 브라우저에 띄우게 되고 캠을 바꾸게 되면 나의 브라우저에서는 바뀌지만 상대방의 브라우저에는 변화가 없다.

이를 고치기 위해서 peer에게 줄 stream 업데이트를 해야한다.

async function handleCamChange() {
  //나의 캠 바꾸기
  await getMedia(camSelect.value);
  //바꾼 나의 캠 상대방에게 보여주기
  if (myPeerConnection) {
    // myPeerConnection.getSenders() 여기에는 오디오와 영상을 보내는 sender가 있다.;
    // sender는 우리의 peer로 보낸진 media stream track을 컨트롤 하게 해준다. mdn - RTCRtpSender
    // 나의 장치를 getVideoTracks로 가져온다.
    const videoTrack = stream.getVideoTracks()[0];
    const videoSender = myPeerConnection
      .getSenders()
      .find((sender) => sender.track.kind === "video");
    videoSender.replaceTrack(videoTrack);
  }
}

 

STUN / TURN 서버

 

이렇게 만든 사이트는 폰으로 접속을 하면 작동하지 않을 것이다. 이를 위해 STUN 혹은 TURN 서버라는 것이 필요하다.

(와이파이에 둘 다 있으면 같은 ip이기에  연결이 된다.)

STUN 서버는 어떤 것을 request 하면 인터넷에서 내가 누군지 알려주는 서버이다.

테스트용으로 무료로 제공하는 서버를 사용해 확인해보자

나만의 서비스를 만들려면 본인 소유의 STUN 서버를 운영해야한다.

절대로  구글이 제공하는 걸 사용하지 말자

async function makePeerConnection() {
  myPeerConnection = new RTCPeerConnection({
    iceServers: [
      {
        urls: [
          //개인 고유의 stun 서버가 필요하다
          //아래는 테스트용으로만 사용
          "stun:stun.l.google.com:19302",
          "stun:stun1.l.google.com:19302",
          "stun:stun2.l.google.com:19302",
          "stun:stun3.l.google.com:19302",
          "stun:stun4.l.google.com:19302",
        ],
      },
    ],
  });
  stream
    .getTracks()
    .forEach((track) => myPeerConnection.addTrack(track, stream));
  myPeerConnection.addEventListener("icecandidate", handleICECandidate);
  myPeerConnection.addEventListener("track", handleAddTrack);
}

 

일단 이를 확인하기 위해서 Local Tunnel 설치해야한다.

npm i localtunnel
npm i -g localtunnel

**둘다 설치

local tunnel은 일시적으로 서버를 전세계와 공유하게 해준다. (일시적이라 함은 기간이 아니라 서버가 종료되면 없어진다.)

lt --port 3000 //(내가 지정한 포트숫자)

이렇게 하면 특정 url이 콘솔창에 나온다.

들어가게 되면 EndPoint ip 주소를 입력하게 되있는데 열린 브라우저내에서 

  1. If you're running localtunnel on a local computer, go to this link in your browser: 링크

링크를 눌러 입력후 submit 하면 된다. 

** ip 주소창은 닫지 않고 핸드폰으로 해당 URL로 들어가 똑같이 입력해주자

 

  • **data channel로 상대방에게 무언가를 보내보자
728x90

'SocketIO & WebRTC > WebRTC' 카테고리의 다른 글

WebRTC (SocketIO / Offer / Answer )__002  (0) 2023.11.23
WebRTC (User Video)__001  (0) 2023.11.22
반응형

Room Create

방에 들어갈수 있는 적당한 양식의 Element를 짜주고 SocketIO와 함께 사용하여 서버와 연결해준다.

async function handleRoomCreate(event) {
  event.preventDefault();
  roomName = roomNameInput.value;
  await getMedia();
  socket.emit("room_create", roomName);
  roomNameInput.value = "";
}

socket.on("join_room", () => {
 console.log('join')
});

방의 입장과 동시에 장치들을 불러와준다.

 

서버에서는 방을 만들고 방에 입장할수 있게 코드를 짜준다.

//server.js
const httpServer = http.createServer(app);
const wsServer = SocketIO(httpServer);

wsServer.on("connection", (socket) => {
  socket.on("room_create", (roomName) => {
    socket.join(roomName);
    socket.to(roomName).emit("join_room");
  });
});

WebRTC

방의 입장과 동시 여기 이미지대로 따라하기만 하면된다. WebRTC의 동작원리이다.

첫번째는 양 브라우저의 peerConnection을 만드는 것이다.

async function makePeerConnection() {
  myPeerConnection = new RTCPeerConnection();
}

후에 방을 만들면서 peerConnection을 활성화 하기 위해 function을 배치한다.

async function handleRoomCreate(event) {
  event.preventDefault();
  roomName = roomNameInput.value;
  await getMedia();
  makePeerConnection();
  socket.emit("room_create", roomName);
  roomNameInput.value = "";
}

connection이 연결을 하기 위한 준비가되었으면 우리는 각각 브라우저에게 전달해줄 영상과 오디오를 넣어줘야한다.

async function makePeerConnection() {
  myPeerConnection = new RTCPeerConnection();
  stream
    .getTracks()
    .forEach((track) => myPeerConnection.addTrack(track, stream));

}

두번째는 CreateOffer를 만들어야한다.

도식표에 보면 Peer A 가 Peer B에게 offer를 건내 주는 게 그려져있다. 

이를 위해 SocketIO를 통해 Offer를 날려주자.

Offer는 쉽게 말해 다른 브라우저가 참가할 수 있도록 초대장을 만드는것이다.

Offer를 만들고 연결을 구성하기위해 setLocalDescription을 사용한다.

초대장을 타인에게 날려주기위해 SocketIO로 날려준다.

socket.on("join_room", async () => {
  const offer = await myPeerConnection.createOffer();
  await myPeerConnection.setLocalDescription(offer);
  socket.emit("send_offer", roomName, offer);
});

//server.js
wsServer.on("connection", (socket) => {
  socket.on("room_create", (roomName) => {
    socket.join(roomName);
    socket.to(roomName).emit("join_room");
  });
  socket.on("send_offer", (roomname, offer) => {
    socket.to(roomname).emit("receive_offer", offer);
  });
});

보내준 Offer를 받는 것은 Peer B의 setRemoteDescription이다.

**방을 만들때 장치를 불러오고 peerConnection을 socket으로 동작하게 되면 너무 빨리 데이터가 통신되는 바람에 offer가 안가니 socket으로 보내주지 않도록 하자

받은 offer를 통해 answer를 해줘야한다. 이를 위해 createAnswer를 사용해주고 offer에대한 연결을 해주기위해 setLocalDescription을 해준다. 

후에 타 브라우저가 방에 도착함을 answer를 SocketIO를 통해 보내준다.

socket.on("receive_offer", async (offer) => {
  myPeerConnection.setRemoteDescription(offer);
  const receivedDone = await myPeerConnection.createAnswer();
  myPeerConnection.setLocalDescription(receivedDone);
  socket.emit("received_doneSig", receivedDone, roomName);
});

이렇게 초대장을 보내고 그에 따른 답장을 받고 초대되어 온 친구를 환영하기 위해 

setRemoteDescription을Answer로 담아주면 된다.

//server.js

wsServer.on("connection", (socket) => {
  socket.on("room_create", (roomName) => {
    socket.join(roomName);
    socket.to(roomName).emit("join_room");
  });
  socket.on("send_offer", (roomname, offer) => {
    socket.to(roomname).emit("receive_offer", offer);
  });
  socket.on("received_doneSig", (doneSign, roomname) => {
    socket.to(roomname).emit("answer_offer", doneSign);
  });
});
//client.js

socket.on("answer_offer", (doneSign) => {
  myPeerConnection.setRemoteDescription(doneSign);
});

 

 

728x90

'SocketIO & WebRTC > WebRTC' 카테고리의 다른 글

WebRTC (ICE Candidate / Senders / Stun)__003  (0) 2023.11.24
WebRTC (User Video)__001  (0) 2023.11.22
반응형

Call Signatures

함수의 arguments에 직접 타입을 작성하는 방식이 아니다.

// function add (a:number,b:number){
//     return a+b
// }
// console.log(add(1,3))



type TNums=(a:number, b:number)=>number

// const add : TNums = function(a,b){
//     return a+b
// }

// 위와 동일한 문장

const add : TNums=(a,b)=>a+b
console.log(add(1,3))

 

void가 도출된다면 함수 동작구문에 return이 제대로 되지 않은거니 확인을 해야한다.

OverLoading

function overloading / method overloading이라고 많은 사람들이 다양하게 부른다.

실제로 많은 오버로딩된 함수를 직접 작성하지 않을 것이다. 

overloading은 함수가 서로 다른 여러 개의 call signatures를 가지고 있을 때 발생한다.

// ex 1
 type TNums={
    (a:number, b:number) : number
    (a:number, b:string) : number
    }

const add : TNums=(a,b)=>{
    if(typeof b === 'string') return a
    return a+b
    }
console.log(add(1,3))

// ex 2
type Config={
    path:string
    state:object
}
type Push ={
    (path:string):void
    (config:Config):void
}

const  push:Push=(config)=>{
    if(typeof config === 'string')console.log(config)
    else{
        config.path
    } 
}

// ex 3
type Add={
    (a:number,b:number):number
    (a:number,b:number,c:number):number
}

const add :Add=(a,b,c?:number)=>{
    if(c)return a+b+c
    return a+b

}

 

PolymorPhism(다양성 / 다양한 형태)

타입스크립트에서 함수는 다른 2~3개의 parameter를 가질 수 있다. 또는 string이나 object를 첫버냬째 parameter로 가질수 있다.

type SuperPrint={
    (arr:number[]):void
    (arr:boolean[]):void
    (arr:string[]):void
    (arr:(number | boolean | string)[]):void
    (arr:[number | boolean | string]):void
}
const sPring:SuperPrint=(arr)=>{
    arr.forEach(i=>console.log(i))
}
const qwer = [1,2,3,4,5] 

sPring(qwer)
sPring([true,true,false,false])
sPring(['a','b','c'])
sPring([1,true,'soso'])

concrete type (단일 속성) 이 아닌 generic type을 줄 수 있다 . generic이란 타입의 placeholder같은 것이다.

type SuperPrint={
    <TPrint>(arr:TPrint):void
}

// 아래와 같은 경우도 많이 보게 될 것임.
// type SuperPrint={
//    <T>(arr:T[]):T
// }
// type SuperPrint = <T>(a:T[]) => T

const sPring:SuperPrint=(arr)=>{
    arr.forEach(i=>console.log(i))
}

const qwer = [1,2,3,4,5] 

sPring(qwer)
sPring([true,true,false,false])
sPring(['a','b','c'])
sPring([1,true,'soso'])

Generic에 type 추가

type SuperPrint= <T,M>(arr:T[], b:M)=>T

const sPring:SuperPrint = (arr) => arr[0]a


const qwer = [1,2,3,4,5] 

const a = sPring(qwer,"hoho")
const b = sPring([true,true,false,false],123)
const c = sPring(['a','b','c'],false)
const d = sPring([1,true,'soso'],c)

console.log(a)
console.log(b)
console.log(c)
console.log(d)

 

type Player<E>={
    name:string
    extra:E
}
type Extra = {favFd:string}
type HohoPlayer  = Player<Extra>

const hoho :HohoPlayer={
    name:'haha',
    extra:{
        favFd:'toto'
    }
}

type ArrNumbers = Array<number>

let a : ArrNumbers = [1,2,3,4,5]
728x90

+ Recent posts